RLDS3D.CPP

/************************************************************************************************************ 

Direct3DRM and DirectSound3D interface designed for the Viewer Sample Application

(c) 1996 Microsoft Corporation

************************************************************************************************************/


#include "rlds3d.h"
#include "ds3dvi.h" // "Internal" include stuff, like some structures

// For resource symbols
#include "resource.h"

#include "d3drmwin.h"

#include <windows.h>
#include <windowsx.h>

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <direct.h>

#include <objbase.h>
#include <initguid.h>

#include "rodcone.h"
#include "file.h"

#include <mmsystem.h>
#include "dsound.h"
#include "dsutil3d.h"


/*
** This is an interface between the viewer and the RL/DS3D APIs which provides the
** functionality required by the viewer in simplified form.
**
** Note: This is not an object-oriented API since there should only be need for one
** copy at a time and this way C++ to C conversions should be fairly easy
**
** DESCRIPTION: The user interfaces the 3D world and objects therein by selecting an item
** with the mouse (on the screen) and performing operations on that item. The user will
** also be able to perform operations on the camera and a few global operations applied
** to all objects.
**
** USAGE: Create the interface using RLDS3D_Initialize() (returns FALSE if not created)
** and remove it using RLDS3D_Deinitialize(). The Render() functionality draws the world
** on the screen. Other functionality as described.
*/

/*
*********************************** GLOBALS *************************************
*/

/*
** VIEWER LPDIRECT3DRMFRAME APPDATA STRUCTURE
**
** Pointers to this class are placed (as necessary) in the appdata fields for LPDIRECT3DRMFRAMEs. Any
** necessary data to be attached to frames should be added to this class
*/

class FRAMEAPPDATA {
public:
// Sound buffer interfaces (the standard one and the 3D interface)
LPDIRECTSOUNDBUFFER Sound;
LPDIRECTSOUND3DBUFFER i3DSound;

// Whether or not this frame is currently in orbit (and thus contained in a temporary frame)
BOOL bOrbiting;
// Whether or not this frame is a bullet (and thus has a bullet callback and dialog)
BOOL bBullet;
// A window handle associated with the orbiting/bullet state which controls speed, etc.
HWND hDlg;

FRAMEAPPDATA() : Sound(NULL), i3DSound(NULL), bOrbiting(FALSE), bBullet(FALSE), hDlg(0) {};
~FRAMEAPPDATA() {
if (i3DSound) i3DSound->Release();
if (Sound) Sound->Release();
};
};
typedef FRAMEAPPDATA* LPFRAMEAPPDATA;

// Interface to the D3DRM functions
LPDIRECT3DRM lpD3DRM = NULL;

// Pointer to the Directsound API
LPDIRECTSOUND lpDS = NULL;

// Clipper for the DDraw surface
LPDIRECTDRAWCLIPPER lpDDClipper = NULL;

// Handle to the API's parent window (passed in Initialize)
HWND hwndParent;

// Handle to our instance
HINSTANCE hinst;

AppInfo* info;

/*
** Globals used for editing what is in the D3DRM world
*/

// Selected frame (contains visual currently selected by user)
LPDIRECT3DRMFRAME sFrame = NULL;

// Whether or not editing box should be shown around the selected object
BOOL showBoxes = FALSE;

// Currently selected visual (if one is selected)
static LPDIRECT3DRMMESHBUILDER sVisual = NULL;

// Currently selected light (if one is selected)
static LPDIRECT3DRMLIGHT sLight = NULL;

// Box/Speaker cone to go around the selected item (if boxes are on and if there IS a speaker)
static LPDIRECT3DRMMESH selectionBox = NULL;
static LPDIRECT3DRMMESH selectionSpeaker = NULL;

// Clipboard frame and visual allowing cutting and pasting
LPDIRECT3DRMFRAME clipboardFrame = NULL;
LPDIRECT3DRMVISUAL clipboardVisual = NULL;

/*
** Globals for DirectSound3D
*/

// Listener information (connected to the camera)
LPDIRECTSOUND3DLISTENER lp3DListenerInfo;

// Primary Buffer (we don't REALLY need a pointer to it but we keep it just in case)
LPDIRECTSOUNDBUFFER lpDSBuff;

/*
************************ Internal function declarations ****************************
*/

// These are not part of the interface and are just used by the RLDS3D functions
static BOOL CreateDevice(HWND win, AppInfo* info);
HRESULT __cdecl loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex);
char* LSTRRCHR( const char* lpString, int bChar );
static void PlaceMesh(LPDIRECT3DRMMESHBUILDER mesh, AppInfo *info);
static BOOL CreateScene(AppInfo* info);
static BOOL RebuildDevice(HWND win, AppInfo* info, int width, int height);
static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX*);
void SelectVisual(LPDIRECT3DRMMESHBUILDER visual, LPDIRECT3DRMFRAME frame);
int ChooseNewColor(HWND, D3DCOLOR*);
void RemoveSoundRecord(LPDIRECT3DRMFRAME owner);
void releaseSoundCallback(LPDIRECT3DRMFRAME frame);
void StopOrbiting(LPDIRECT3DRMFRAME stopme);
void UpdateConeVisual(void);

/*
*********************** Error checker functions ************************************
*/

/*
** These are wrapped around DS or D3DRM calls to appropriately catch errors and handle them.
**
** The generalized cases are coded, callers can pass forced reactions to errors by using the force_critical variables
*/

/*
** In this code the general philosophy for when to use the viewer is when requesting anything from something outside the
** program. Thus, asking a frame which we know exists to accept an added visual doesn't get checked, but trying to create
** a new frame (which tries to allocate memory) is checked in case we've run out of free memory.
*/

BOOL D3DRM_SUCCEED(HRESULT result, int force_critical, char* info) {
if (result == D3DRM_OK) return TRUE;
// Could be 0 for non-critical, 1 for non-critical but reports it, or 2 for critical (notify and quit)
int priority = 0;
char* error_string;
switch (result) {
case D3DRMERR_BADALLOC: error_string = "Out of memory"; priority = 2; break;
case D3DRMERR_BADDEVICE: error_string = "Device is not compatible with renderer"; priority = 2; break;
case D3DRMERR_BADFILE: error_string = "Data file is corrupt"; priority = 2; break;
case D3DRMERR_BADMAJORVERSION: error_string = "Bad DLL major version"; priority = 2; break;
case D3DRMERR_BADMINORVERSION: error_string = "Bad DLL minor version"; priority = 2; break;
case D3DRMERR_BADOBJECT: error_string = "Object expected in argument"; priority = 2; break;
case D3DRMERR_BADTYPE: error_string = "Bad argument type passed"; priority = 1; break;
case D3DRMERR_BADVALUE: error_string = "Bad argument value passed"; priority = 1; break;
case D3DRMERR_FACEUSED: error_string = "Face already used in a mesh"; priority = 1; break;
case D3DRMERR_FILENOTFOUND: error_string = "File cannot be opened"; priority = 1; break;
case D3DRMERR_NOTDONEYET: error_string = "Unimplemented function called"; priority = 1; break;
case D3DRMERR_NOTFOUND: error_string = "Object not found in specified place"; priority = 1; break;
case D3DRMERR_UNABLETOEXECUTE: error_string = "Unable to carry out procedure"; priority = 2; break;
default: error_string = "D3DRM Error: Unable to continue"; priority = 2; break;
}

int ret;
if (force_critical >= 0) priority = force_critical;
if (priority == 1) {
ret = MessageBox(hwndParent, error_string, "D3DRM Warning", MB_APPLMODAL|MB_ICONWARNING|MB_OK);
}
else if (priority == 2) {
ret = MessageBox(hwndParent, error_string, "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
PostMessage(hwndParent, WM_CLOSE,0,0);
}
return FALSE;
}

/*
** DS isn't vital for the viewer to run so error messages don't force a quit
*/

BOOL DS3D_SUCCEED(HRESULT result, int force_critical, char* info) {
if (result == DS_OK) return TRUE;
// Could be 0 for no message, 1 for non-critical (simply needs to report it), or 2 for critical (notify and quit)
int priority = 0;
char* error_string = NULL;
switch (result) {
case DSERR_ALLOCATED: error_string = "Requested resources already in use"; priority = 1; break;
case DSERR_ALREADYINITIALIZED: error_string = "Object already initialized"; priority = 0; break;
case DSERR_BADFORMAT: error_string = "Wave format not supported"; priority = 0; break;
case DSERR_BUFFERLOST: error_string = "Buffer lost and must be restored"; priority = 0; break;
case DSERR_CONTROLUNAVAIL: error_string = "Control requested not available"; priority = 0; break;
case DSERR_GENERIC: error_string = "Undetermined error"; priority = 1; break;
case DSERR_INVALIDCALL: error_string = "Invalid call for object's current state"; priority = 0; break;
case DSERR_INVALIDPARAM: error_string = "Invalid parameters passed to object"; priority = 0; break;
case DSERR_NOAGGREGATION: error_string = "Object does not support aggregation"; priority = 1; break;
case DSERR_NODRIVER: error_string = "No sound driver available"; priority = 1; break;
// case DSERR_NOINTERFACE: error_string = "Requested COM interface not available"; priority = 0; break;
case DSERR_OUTOFMEMORY: error_string = "Out of memory"; priority = 1; break;
case DSERR_PRIOLEVELNEEDED: error_string = "Caller does not have required priority level"; priority = 0; break;
// case DSERR_UNINITIALIZED: error_string = "DirectSound not initialized"; priority = 1; break;
case DSERR_UNSUPPORTED: error_string = "Unsupported function called"; priority = 0; break;
default: error_string = "Undetermined error"; priority = 1; break;
}

int ret;
if (force_critical >= 0) priority = force_critical;
if (priority == 1) {
ret = MessageBox(hwndParent, error_string, "DS3D Warning", MB_APPLMODAL|MB_ICONWARNING|MB_OK);
}
else if (priority == 2) {
ret = MessageBox(hwndParent, error_string, "DS3D Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
PostMessage(hwndParent, WM_CLOSE,0,0);
}
return FALSE;
}




/*
************************ INITIALIZATION/DEINITIALIZATION ****************************
*/

/*
** Initialize will attach itself to the passed window. Call initialize after
** getting a handle for your window but before you display it.
**
** Initialize will initialize the D3DRM API and return false if it fails. It will
** also attempt to initialize a DirectSound3D API, but failing this does not
** justify a failed Initialize (since the 3D sound isn't a necessary part of
** the viewer)
*/

BOOL RLDS3D_Initialize(HWND hwndPW, HINSTANCE this_inst) {

if (!hwndPW) return FALSE;

hinst = this_inst;
hwndParent = hwndPW;

// D3DRM API INIT - we force non-critical warnings here because the initialization returns a boolean for success

// Call Direct3DRMCreate to try to make a D3DRM object
if (!D3DRM_SUCCEED(Direct3DRMCreate(&lpD3DRM), 1)) return FALSE;


if (!D3DRM_SUCCEED(DirectDrawCreateClipper(0, &lpDDClipper, NULL),1)) {
lpD3DRM->Release();
return FALSE;
}

if (!D3DRM_SUCCEED(lpDDClipper->SetHWnd(0, hwndParent),1)) {
lpDDClipper->Release();
lpD3DRM->Release();
return FALSE;
}


// Set up D3DRM information structure
info = (AppInfo*) malloc(sizeof(AppInfo));
if (!info) {
lpDDClipper->Release();
lpD3DRM->Release();
return FALSE;
}

info->model = D3DCOLOR_MONO;

// Calls our internal function to create a viewport and device and attach it to the window (CreateDevice in misc. functionality)
// (also fills in the global information structure "info" for what D3DRM can do and is currently doing)
if (CreateDevice(hwndParent, info) == FALSE) {
lpDDClipper->Release();
lpD3DRM->Release();
return FALSE;
}

// DIRECTSOUND 3D INIT - note, if this fails the entire initialization is still OK.

// Description for our primary buffer creation
DSBUFFERDESC dsbd;

int ret = IDRETRY;

// Try to create the Directsound objects until we either do it, are told to ignore it, or are told to abort
while (ret == IDRETRY) {
// Create the directsound object
if (DS3D_SUCCEED(DirectSoundCreate(NULL, &lpDS, NULL))) {
// Set cooperative level
if (DS3D_SUCCEED(lpDS->SetCooperativeLevel(hwndParent, DSSCL_PRIORITY))) {
// Create a primary buffer so we can query for a 3D Listener interface
memset(&dsbd, 0, sizeof(DSBUFFERDESC));
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D;
if (DS3D_SUCCEED(lpDS->CreateSoundBuffer(&dsbd, &lpDSBuff, NULL))) {

// Make the primary 44.1 KHz so that it sounds better
WAVEFORMATEX wfx;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 44100;
wfx.nAvgBytesPerSec = 44100*2*2;
wfx.nBlockAlign = 4;
wfx.wBitsPerSample = 16;
wfx.cbSize = 0;
lpDSBuff->SetFormat(&wfx);

// Get the 3D listener information (error currently ignored)
if (DS3D_SUCCEED(lpDSBuff->QueryInterface(IID_IDirectSound3DListener, (void**) &lp3DListenerInfo))) {
lp3DListenerInfo->SetDopplerFactor(D3DVAL(100.0), DS3D_IMMEDIATE);
}
else {
// Failed to get listener info
lpDSBuff->Release();
lpDS->Release();
lpDS = NULL;
}
}
else {
// Failed to create a primary buffer
lpDS->Release();
lpDS = NULL;
}
}
else {
// Failed to set cooperative level
lpDS->Release();
lpDS = NULL;
}
}

// Warn that we could create the DirectSound object
if (!lpDS) {
ret = MessageBox(hwndParent, "DirectSound 3D could not initialize", "Warning", MB_APPLMODAL|MB_ICONWARNING|MB_ABORTRETRYIGNORE);
if (ret == IDABORT) {
lpDDClipper->Release();
lpD3DRM->Release();
return FALSE;
}
}
else ret = IDOK;
}
return TRUE;
}

/*
** Deinitializes
*/

void RLDS3D_Deinitialize() {
// Releases the DSound interface... this is very important!
if (lpDS != NULL) {
lpDSBuff->Release();
lpDS->Release();
}
info->dev->Release();
lpDDClipper->Release();
lpD3DRM->Release();
free(info);
}

/*
******************************************** ADDING/REMOVING/EDITING OBJECTS *********************************
*/

/*
** Loads XOF file into the RL world (with textures)
*/

void RLDS3D_LoadXOF(char* file) {
if (!file) return;
LPDIRECT3DRMMESHBUILDER builder = NULL;
if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (builder->Load(file, NULL, D3DRMLOAD_FROMFILE, loadTextures, NULL) != D3DRM_OK) {
MessageBox(hwndParent, "Unable to load file", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONEXCLAMATION|MB_OK);
builder->Release();
return;
}
PlaceMesh(builder, info);
builder->Release();
}
}

/*
** Sets/Gets whether or not boxes are shown around selected item
*/

BOOL RLDS3D_GetBoxes(void) {
return showBoxes;
}

void RLDS3D_SetBoxes(BOOL new_val) {
showBoxes = new_val;
// Re-selects the currently selected visual so that the box is generated/destroyed around it
RLDS3D_UpdateSelectionBox();
}

/*
** Updates the bounding box around the selected visual (this could be done using a render callback function to compare the
** frame's scaling and transform functions instead)
*/

void RLDS3D_UpdateSelectionBox(void) {
// When we select this visual, we destroy any existing box and create a new one around it if showBoxes is true.
SelectVisual(sVisual, sFrame);
}

/*
** Deselects the currently selected 3D visual.
*/

void RLDS3D_DeselectVisual()
{
// Removes the bounding box from around it if it's there
if (sFrame && selectionBox) {
sFrame->DeleteVisual(selectionBox);
sFrame->DeleteVisual(selectionSpeaker);
}


sFrame = NULL;
sLight = NULL;
sVisual = NULL;
selectionBox = NULL;
selectionSpeaker = NULL;
}

/*
** Given coordinates it selects the first visual under those coordinates in the window's viewport
*/

void RLDS3D_FindAndSelectVisual(int x, int y, LPBOOL changed) {
LPDIRECT3DRMVISUAL visual;
LPDIRECT3DRMFRAME frame;
LPDIRECT3DRMPICKEDARRAY picked;
LPDIRECT3DRMFRAMEARRAY frames;
LPDIRECT3DRMMESHBUILDER mesh;
LPDIRECT3DRMVIEWPORT view = info->view;

LPDIRECT3DRMFRAME oldframe = sFrame;

/*
* Make sure we don't try to select the selection box of the current
* selection.
*/
RLDS3D_DeselectVisual();

view->Pick(x, y, &picked);
if (picked)
{ if (picked->GetSize())
{
// Get the top-level visual
picked->GetPick(0, &visual, &frames, 0);
// The frames that contain the visual are placed into a framearray in heiarchical order, take the
// last one (the one most closely associated with the visual)
frames->GetElement(frames->GetSize() - 1, &frame);
// We can only select meshes so we query the visual to make sure it is one
if (D3DRM_SUCCEED(visual->QueryInterface(IID_IDirect3DRMMeshBuilder, (void **) &mesh), 0))
{
// If we're clicking on an orbiting frame then we need to stop it.
StopOrbiting(frame);
SelectVisual(mesh, frame);
mesh->Release();
}
frame->Release();
frames->Release();
visual->Release();
}
picked->Release();
}
if (changed) {
if (sFrame == oldframe) *changed = FALSE; else *changed = TRUE;
}
}

/*
** Cuts the current selection to the clipboard
*/

void RLDS3D_CutVisual()
{
LPDIRECT3DRMFRAME frame;

if (clipboardFrame)
clipboardFrame->Release();

if (sFrame)
{ clipboardFrame = sFrame;
clipboardVisual = sVisual;

// If a 3D sound is attached, remove it (sounds not carried to clipboard)
RemoveSoundRecord(clipboardFrame);
RLDS3D_DeselectVisual();

clipboardFrame->AddRef();
clipboardFrame->GetParent(&frame);
if (frame) {
frame->DeleteChild(clipboardFrame);
frame->Release();
}
}
}

/*
** Copies the current selection to the clipboard
*/

void RLDS3D_CopyVisual()
{
LPDIRECT3DRMFRAME frame;

if (sFrame)
{
// Things could really foul up if the clones aren't created so we make sure they are
if (!D3DRM_SUCCEED(sFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &clipboardFrame))) {
// If we've failed, we must make sure the clipboard is empty!
clipboardVisual->Release();
clipboardVisual = NULL;
return;
}

if (!D3DRM_SUCCEED(sVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &clipboardVisual))) {
// If we've failed, we must make sure the clipboard is empty!
clipboardFrame->Release();
clipboardFrame = NULL;
return;
}

// If a 3D sound is attached, remove it (sounds not carried to clipboard)
RemoveSoundRecord(clipboardFrame);

clipboardFrame->AddVisual(clipboardVisual);
clipboardVisual->Release();

clipboardFrame->GetParent(&frame);
if (frame) {
frame->DeleteChild(clipboardFrame);
frame->Release();
}
}
}

/*
** Pastes the current selection to the window
*/

void RLDS3D_PasteVisual()
{
if (clipboardFrame)
{
LPDIRECT3DRMFRAME frame;
LPDIRECT3DRMVISUAL visual;

if (!D3DRM_SUCCEED(clipboardFrame->Clone(0, IID_IDirect3DRMFrame, (void **) &frame))) return;
if (!D3DRM_SUCCEED(clipboardVisual->Clone(0, IID_IDirect3DRMVisual, (void **) &visual))) {
frame->Release();
return;
}

frame->AddVisual(visual);
info->scene->AddChild(frame);
visual->Release();
frame->Release();
}
}

/*
** Deletes the current selection from the world without copying to the clipboard
*/

void RLDS3D_DeleteVisual()
{
if (sFrame) {
LPDIRECT3DRMFRAME parent, frame;
// Make a copy of the selected frame ('cause deselecting it will make it inaccessible)
frame = sFrame;
// If a 3D sound is attached, remove it
RemoveSoundRecord(frame);

// Deselect the frame (removes bounding box, etc, also sets sFrame to NULL)
RLDS3D_DeselectVisual();
if (frame->GetAppData()) delete (FRAMEAPPDATA*)frame->GetAppData();
frame->GetParent(&parent);
if (parent) {
parent->DeleteChild(frame);
parent->Release();
}
}
}

/*
** Add a directional light
*/

void RLDS3D_AddDirectionalLight() {
LPDIRECT3DRMMESHBUILDER builder;
LPDIRECT3DRMLIGHT light;
LPDIRECT3DRMFRAME frame;

if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (D3DRM_SUCCEED(builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
builder->SetQuality(D3DRMRENDER_UNLITFLAT);
if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
frame->AddVisual(builder);
frame->AddLight(light);
frame->Release();
}
light->Release();
}
}
builder->Release();
}
}

/*
** Add a parallel point light
*/

void RLDS3D_AddParallelPointLight() {
LPDIRECT3DRMMESHBUILDER builder;
LPDIRECT3DRMLIGHT light;
LPDIRECT3DRMFRAME frame;

if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (D3DRM_SUCCEED(builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
builder->SetQuality(D3DRMRENDER_UNLITFLAT);
builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2));
if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_PARALLELPOINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
frame->AddVisual(builder);
frame->AddLight(light);
frame->Release();
}
light->Release();
}
}
builder->Release();
}
}

/*
** Add Point Light
*/

void RLDS3D_AddPointLight() {
LPDIRECT3DRMMESHBUILDER builder;
LPDIRECT3DRMLIGHT light;
LPDIRECT3DRMFRAME frame;

if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (D3DRM_SUCCEED(builder->Load("sphere2.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
builder->SetQuality(D3DRMRENDER_UNLITFLAT);
builder->Scale(D3DVAL(0.2), D3DVAL(0.2), D3DVAL(0.2));
if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_POINT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
frame->AddVisual(builder);
frame->AddLight(light);
frame->Release();
}
light->Release();
}
}
builder->Release();
}
}

/*
** Add a spotlight
*/

void RLDS3D_AddSpotlight() {
LPDIRECT3DRMMESHBUILDER builder;
LPDIRECT3DRMLIGHT light;
LPDIRECT3DRMFRAME frame;

if (D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&builder))) {
if (D3DRM_SUCCEED(builder->Load("camera.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL))) {
builder->SetQuality(D3DRMRENDER_UNLITFLAT);
if (D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_SPOT, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light))) {
if (D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) {
frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(10.0));
frame->AddVisual(builder);
frame->AddLight(light);
frame->Release();
}

light->Release(); 
}
}
builder->Release();
}
}


/*
*********************************** OBJECT MOTION/SCALING/COLORING ****************************************
*/

/*
** Chooses a new color and sets the selected object to it... handles the selection of the color, etc.
*/

void RLDS3D_SetSelColour(void) {
if (!sFrame) return;
LPDIRECT3DRMMESHBUILDER mesh;

if (!D3DRM_SUCCEED(sVisual->QueryInterface(IID_IDirect3DRMMeshBuilder, (void**) &mesh),0)) return;

if (sLight)
{
D3DCOLOR c = sLight->GetColor();

if (ChooseNewColor(hwndParent, &c)) {
mesh->SetColor(c);
sLight->SetColor(c);
}
} else {
D3DCOLOR c;

if (mesh->GetFaceCount()) {
LPDIRECT3DRMFACEARRAY faces;
LPDIRECT3DRMFACE face;
mesh->GetFaces(&faces);
faces->GetElement(0, &face);
c = face->GetColor();
face->Release();
faces->Release();
} else
c = D3DRMCreateColorRGB(D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));

if (ChooseNewColor(hwndParent, &c))
mesh->SetColor(c);
}

mesh->Release();
}


/*
** Moves the camera relative to itself by providing scalars to multiply against the CAMERA-RELATIVE unit vectors
** forwards/up/right.
*/

void RLDS3D_SetCamVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right) {
D3DVECTOR vDir, vUp, vRight;
info->camera->GetOrientation(info->scene, &vDir, &vUp);
// Cross the UP vector and the RIGHT vector to get the FORWARDS vector
D3DRMVectorCrossProduct(&vRight, &vUp, &vDir);
info->camera->SetVelocity(info->scene, vDir.x*forward + vUp.x*up + vRight.x*right,
vDir.y*forward + vUp.y*up + vRight.y*right,
vDir.z*forward + vUp.z*up + vRight.z*right,
TRUE);
}

/*
** Rotates the camera around its three axis
**
** forward_axis is roll, up_axis is yaw, right_axis is pitch
*/

void RLDS3D_SetCamRotForward(D3DVALUE forward_axis) {
info->camera->SetRotation(info->camera, 0.0f, 0.0f, 1.0f, forward_axis);
}

void RLDS3D_SetCamRotUp(D3DVALUE up_axis) {
info->camera->SetRotation(info->camera, 0.0f, 1.0f, 0.0f, up_axis);
}

void RLDS3D_SetCamRotRight(D3DVALUE right_axis) {
info->camera->SetRotation(info->camera, 1.0f, 0.0f, 0.0f, right_axis);
}

/*
** Scales the currently selected object by the scale factors...
*/

void RLDS3D_ScaleSelected(D3DVALUE sx, D3DVALUE sy, D3DVALUE sz) {
if (!sFrame) return;
if (!sVisual) return;
RLDS3D_StopOrbitSelected();
sVisual->Scale(sx, sy, sz);
RLDS3D_UpdateSelectionBox();
}

/*
** Moves the currently selected object in the 3D world relative to the camera
*/

void RLDS3D_SetSelectedVelRelToCam(D3DVALUE forward, D3DVALUE up, D3DVALUE right) {
if (!sFrame) return;
RLDS3D_StopOrbitSelected();
D3DVECTOR vDir, vUp, vRight;
info->camera->GetOrientation(info->scene, &vDir, &vUp);
D3DRMVectorCrossProduct(&vRight, &vUp, &vDir);
sFrame->SetVelocity(info->scene, vDir.x*forward + vUp.x*up + vRight.x*right,
vDir.y*forward + vUp.y*up + vRight.y*right,
vDir.z*forward + vUp.z*up + vRight.z*right,
TRUE);
}

/*
** Rotates the currently selected object relative to the camera's frame when passed
** a vector for the axis and an angle of rotation.
** This is useful because the AXIS which is specified is relative to what appears on the screen with it's origin at the camera
** (ie: (0,0,1) will be an axis straight into the screen and thus things will spin around the centre of the screen)
*/

void RLDS3D_SetSelectedRotRelToCam(D3DVALUE AxisX, D3DVALUE AxisY, D3DVALUE AxisZ, D3DVALUE angle) {
if (!sFrame) return;
RLDS3D_StopOrbitSelected();
sFrame->SetRotation(info->camera, AxisX, AxisY, AxisZ, angle);
}

/*
** Moves the currently selected object by x/y pixels on the screen from it's relative position. Allows a user
** to drag objects around the screen using the mouse. Assumes that the distance from the frame to the screen
** will remain constant
*/

void RLDS3D_MoveSelectedPosByScreenCoords(double delta_x, double delta_y) {
if (!sFrame) return;
RLDS3D_StopOrbitSelected();
D3DVECTOR p1;
D3DRMVECTOR4D p2;
sFrame->GetPosition(info->scene, &p1);
info->view->Transform(&p2, &p1);
// returned value is in homogenous coords so we need to multiply by w to get vector coords
p2.x += D3DMultiply(D3DVAL((D3DVALUE)delta_x), p2.w);
p2.y += D3DMultiply(D3DVAL((D3DVALUE)delta_y), p2.w);
info->view->InverseTransform(&p1, &p2);
sFrame->SetPosition(info->scene, p1.x, p1.y, p1.z);
}

void RLDS3D_GetDistanceFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetDistanceFactor(temp);
}

void RLDS3D_GetDopplerFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetDopplerFactor(temp);
}

void RLDS3D_GetRolloffFactor(D3DVALUE *temp) {
lp3DListenerInfo->GetRolloffFactor(temp);
}

void RLDS3D_SetDistanceFactor(D3DVALUE temp) {
lp3DListenerInfo->SetDistanceFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetDopplerFactor(D3DVALUE temp) {
lp3DListenerInfo->SetDopplerFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetRolloffFactor(D3DVALUE temp) {
lp3DListenerInfo->SetRolloffFactor(temp, DS3D_IMMEDIATE);
}

void RLDS3D_CommitDeferredSettings(void) {
lp3DListenerInfo->CommitDeferredSettings();
}

BOOL RLDS3D_SoundSelected(void) {
if (!lpDS) return FALSE;
if (!sFrame) return FALSE;
if (!sFrame->GetAppData()) return FALSE;
if (!((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound) return FALSE;
return TRUE;
}

void RLDS3D_GetSelConeAngles(LPDWORD inner, LPDWORD outer) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetConeAngles(inner, outer);
}

void RLDS3D_GetSelConeOutsideVolume(LPLONG temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetConeOutsideVolume(temp);
}

void RLDS3D_GetSelMinimumDistance(D3DVALUE *temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetMinDistance(temp);
}

void RLDS3D_GetSelMaximumDistance(D3DVALUE *temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->GetMaxDistance(temp);
}

void RLDS3D_SetSelConeAngles(DWORD inner, DWORD outer) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetConeAngles(inner, outer, DS3D_IMMEDIATE);
UpdateConeVisual();
}

void RLDS3D_SetSelConeOutsideVolume(LONG temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetConeOutsideVolume(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetSelMinimumDistance(D3DVALUE temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetMinDistance(temp, DS3D_IMMEDIATE);
}

void RLDS3D_SetSelMaximumDistance(D3DVALUE temp) {
if (!RLDS3D_SoundSelected) return;
((LPFRAMEAPPDATA)(sFrame->GetAppData()))->i3DSound->SetMaxDistance(temp, DS3D_IMMEDIATE);
}





/*
** Orbit control windows have a pointer to the orbiting frame attached to them. The frame, in return, has the handle of the window in it's
** frameappdata. When the window is used (the value modified/Stop Orbit button hit) the window modifies the frame and itself appropriately.
** When the frame is modified (ie: Destroyed) it passes messages to the window appropriately.
*/

/*
** Structure to store info about an orbit
*/

struct ORBITDATA {
LPDIRECT3DRMFRAME orbit_frame;
LPDIRECT3DRMFRAME child_frame;
D3DVECTOR axis; // Axis around which the orbit is going
D3DVALUE speed; // Speed (angles per second)
};

typedef ORBITDATA* LPORBITDATA;

/*
** Structure to store info about a bullet, used by both the dialog box and the callback function
*/

struct BULLETDATA {
LPDIRECT3DRMFRAME bullet_frame;
D3DVECTOR vStartPosition;
D3DVECTOR vDirection;
D3DVALUE fSpeed;
D3DVALUE fTime;
};

typedef BULLETDATA* LPBULLETDATA;

static void CDECL bulletCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta) {
LPBULLETDATA binfo = (LPBULLETDATA)arg;
if (binfo->fTime <= D3DVAL(0.0)) {
binfo->fTime = D3DVAL(10.0); // Ten seconds, 5 for before and 5 for after.
D3DVALUE mult = D3DDivide(D3DMultiply(binfo->fTime, binfo->fSpeed), D3DVAL(-2.0)); // Calculate the displacement multiplier for calculating starting position
obj->SetPosition(info->scene, binfo->vStartPosition.x + D3DMultiply(mult, binfo->vDirection.x),
binfo->vStartPosition.y + D3DMultiply(mult, binfo->vDirection.y),
binfo->vStartPosition.z + D3DMultiply(mult, binfo->vDirection.z));
}
else binfo->fTime -= delta;
}


/*
** Stops the passed frame from orbiting or from bulleting
*/

void StopOrbiting(LPDIRECT3DRMFRAME stopme) {
// If it isn't orbiting we don't need to stop it!
if (!stopme) return;
if (!stopme->GetAppData()) return;
FRAMEAPPDATA* data = (FRAMEAPPDATA*)stopme->GetAppData();
if (data->bOrbiting) {
if (data->hDlg) {
// We explicitly call EndDialog on the orbit's control without sending it any message. As a result we have
// to grab it's associated orbitdata and delete it ourselves.
LPORBITDATA orbit_data = (LPORBITDATA)GetWindowLong(data->hDlg, DWL_USER);
if (orbit_data) delete orbit_data;
EndDialog(data->hDlg, TRUE);
}

// Get its parent
LPDIRECT3DRMFRAME parent;
stopme->GetParent(&parent);

// Put this frame back into the scene frame (this assumes that's where it came from)
info->scene->AddChild(stopme);

// The parent frame of an orbiting visual is only around to make the object orbit, so we don't need it
parent->Release();
data->bOrbiting = FALSE;
data->hDlg = 0;
}
if (data->bBullet) {
if (data->hDlg) {
LPBULLETDATA bullet_data = (LPBULLETDATA)GetWindowLong(data->hDlg, DWL_USER);
stopme->SetPosition(info->scene, bullet_data->vStartPosition.x, bullet_data->vStartPosition.y, bullet_data->vStartPosition.z);
if (bullet_data) delete bullet_data;
EndDialog(data->hDlg, TRUE);
stopme->DeleteMoveCallback(bulletCallback, bullet_data);
}
stopme->SetVelocity(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), TRUE);
data->bBullet = FALSE;
data->hDlg = 0;
}
}

void RLDS3D_StopOrbitSelected() {
StopOrbiting(sFrame);
}

/*
** Windows procedure for the orbit dialog box
*/

BOOL CALLBACK OrbitDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
lparam = lparam;

LPORBITDATA my_data = (ORBITDATA*)GetWindowLong(win, DWL_USER);
char lpszTS[100];

switch (msg)
{
case WM_INITDIALOG:
// We've set it up to pass in the pointer to our case-specific data (through WM_INITDIALOG)
// so we tuck it away for later use. This points to the ORBITDATA structure associated with
// this dialog box
SetWindowLong(win, DWL_USER, lparam);
sprintf(lpszTS, "%f", ((LPORBITDATA)lparam)->speed);
SendDlgItemMessage(win, IDC_ORBIT, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_ORBIT, EM_SETLIMITTEXT, 100, 0);
return TRUE;

case WM_COMMAND:
{
if (wparam == IDOK) {
// Stopping the orbit will end the dialog for us.
StopOrbiting(my_data->child_frame);
return TRUE;
}
else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_ORBIT) {
// Get string length
int stringlength = SendDlgItemMessage(win, IDC_ORBIT, EM_LINELENGTH, 0, 0);
// Check to make sure the string isn't too long - atof accepts up to 100 chars
if (stringlength > 99) return TRUE;
lpszTS[0] = (char)stringlength;
// Get string
SendDlgItemMessage(win, IDC_ORBIT, EM_GETLINE, 0, (LPARAM) lpszTS);
lpszTS[stringlength] = 0;
// Store speed and set new rotation
my_data->speed = D3DVAL(atof(lpszTS));
my_data->orbit_frame->SetRotation(info->scene, my_data->axis.x, my_data->axis.y, my_data->axis.z, my_data->speed);
return TRUE;
}
else return FALSE;
}
break;
}
return FALSE;
}

/*
** Windows proc for the bullet dialog box
*/

BOOL CALLBACK BulletDlgProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam)
{
lparam = lparam;

LPBULLETDATA my_data = (BULLETDATA*)GetWindowLong(win, DWL_USER);
char lpszTS[100];

switch (msg)
{
case WM_INITDIALOG:
// We've set it up to pass in the pointer to our case-specific data (through WM_INITDIALOG)
// so we tuck it away for later use. This points to the ORBITDATA structure associated with
// this dialog box
SetWindowLong(win, DWL_USER, lparam);
sprintf(lpszTS, "%f", ((LPBULLETDATA)lparam)->fSpeed);
SendDlgItemMessage(win, IDC_BULLET, WM_SETTEXT, 0, (LPARAM) lpszTS);
SendDlgItemMessage(win, IDC_BULLET, EM_SETLIMITTEXT, 100, 0);
return TRUE;

case WM_COMMAND:
{
if (wparam == IDOK) {
// Stopping the orbit will end the dialog for us.
StopOrbiting(my_data->bullet_frame);
return TRUE;
}
else if (HIWORD(wparam) == EN_UPDATE && LOWORD(wparam) == IDC_BULLET) {
// Get string length
int stringlength = SendDlgItemMessage(win, IDC_BULLET, EM_LINELENGTH, 0, 0);
// Check to make sure the string isn't too long - atof accepts up to 100 chars
if (stringlength > 99) return TRUE;
lpszTS[0] = (char)stringlength;
// Get string
SendDlgItemMessage(win, IDC_BULLET, EM_GETLINE, 0, (LPARAM) lpszTS);
lpszTS[stringlength] = 0;

// Store speed and set
my_data->fSpeed = D3DVAL(atof(lpszTS));
my_data->bullet_frame->SetVelocity(info->scene, D3DMultiply(my_data->vDirection.x, my_data->fSpeed),
D3DMultiply(my_data->vDirection.y, my_data->fSpeed),
D3DMultiply(my_data->vDirection.z, my_data->fSpeed),
TRUE);
return TRUE;
}
else return FALSE;
}
break;
}
return FALSE;
}



/*
** Orbits the currently selected object around the camera... creates a frame which is centred at the camera and contains
** the selected frame, and then rotates that frame around the camera
*/

ULONG orbit_num = 1;

void RLDS3D_OrbitSelected(void) {
if (!sFrame) return;
StopOrbiting(sFrame);
FRAMEAPPDATA* tempdat;

// Need to create orbit data, if not then can't orbit
LPORBITDATA orbitdat = new ORBITDATA;
if (!orbitdat) return;

if (!sFrame->GetAppData()) {
tempdat = new FRAMEAPPDATA();
if (!tempdat) return;
sFrame->SetAppData((ULONG)(tempdat));
}
else {
tempdat = (LPFRAMEAPPDATA)sFrame->GetAppData();
}

// Create a frame. Centre it around the camera. Add the selected frame. Rotate it!
LPDIRECT3DRMFRAME orbiting_frame;

if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &orbiting_frame))) return;

orbiting_frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
orbiting_frame->SetOrientation(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0));
orbiting_frame->AddChild(sFrame);

D3DVECTOR vDir, vUp;
info->camera->GetOrientation(info->scene, &vDir, &vUp);

// Store orbit info
orbitdat->orbit_frame = orbiting_frame;
orbitdat->child_frame = sFrame;
orbitdat->speed = D3DVAL(1.0);
orbitdat->axis.x = vUp.x;
orbitdat->axis.y = vUp.y;
orbitdat->axis.z = vUp.z;

orbiting_frame->SetRotation(info->scene, vUp.x, vUp.y, vUp.z, orbitdat->speed);

// Set the boolean in the frame's appinfo to tell everyone it's orbiting
tempdat->bOrbiting = TRUE;

// Create a dialog box and orbitinfo so that this thing has something to control it
HWND mydial = CreateDialogParam(hinst, "OrbitBox", hwndParent, OrbitDlgProc, (LONG)orbitdat);
if (mydial) {
char name_string[50];
sprintf(name_string, "Orbit #%i", orbit_num++);
SetWindowText(mydial, name_string);
ShowWindow(mydial, SW_SHOW);
UpdateWindow(mydial);
tempdat->hDlg = mydial;
}
}

/*
** Bullet the currently selected object. Bullets have two properties: Speed and bullet-time. The bullet passes through it's original
** position along the path defined by the viewer's original viewpoint (it heads towards the viewer). It takes 5 seconds to get to the
** viewer and five seconds once past the viewer, and continues doing thus until the bullet is stopped. Speed is controlled by the user.
*/

ULONG bullet_num = 1;

void RLDS3D_BulletSelected(void) {
if (!sFrame) return;
StopOrbiting(sFrame);

FRAMEAPPDATA* tempdat;
if (!sFrame->GetAppData()) {
tempdat = new FRAMEAPPDATA();
if (!tempdat) return;
sFrame->SetAppData((ULONG)(tempdat));
}
else {
tempdat = (LPFRAMEAPPDATA)sFrame->GetAppData();
}

// Create a new bullet object and attach our callback to the frame (it will release itself and put itself back
// into position when it's done!)

LPBULLETDATA bs = new BULLETDATA;
if (!bs) return;

sFrame->GetPosition(info->scene, &(bs->vStartPosition));

D3DVECTOR vDir, vUp;
info->camera->GetOrientation(info->scene, &vDir, &vUp);
// velocity heading towards camera
bs->vDirection.x = -vDir.x;
bs->vDirection.y = -vDir.y;
bs->vDirection.z = -vDir.z;

bs->bullet_frame = sFrame;
bs->fTime = D3DVAL(10.0); // Ten seconds, 5 for before and 5 for after.
bs->fSpeed = D3DVAL(10.0);

D3DVALUE mult = D3DDivide(D3DMultiply(bs->fTime, bs->fSpeed), D3DVAL(-2.0)); // Calculate the displacement multiplier for calculating starting position

sFrame->SetPosition(info->scene, bs->vStartPosition.x + D3DMultiply(mult, bs->vDirection.x),
bs->vStartPosition.y + D3DMultiply(mult, bs->vDirection.y),
bs->vStartPosition.z + D3DMultiply(mult, bs->vDirection.z));

sFrame->SetVelocity(info->scene, D3DMultiply(bs->vDirection.x, bs->fSpeed),
D3DMultiply(bs->vDirection.y, bs->fSpeed),
D3DMultiply(bs->vDirection.z, bs->fSpeed),
TRUE);

sFrame->AddMoveCallback(bulletCallback, (void*)bs);

// Set the boolean in the frame's appinfo to tell everyone it's orbiting
tempdat->bBullet = TRUE;

// Create a dialog box and orbitinfo so that this thing has something to control it
HWND mydial = CreateDialogParam(hinst, "BulletBox", hwndParent, BulletDlgProc, (LONG)bs);
if (mydial) {
char name_string[50];
sprintf(name_string, "Bullet #%i", bullet_num++);
SetWindowText(mydial, name_string);
ShowWindow(mydial, SW_SHOW);
UpdateWindow(mydial);
tempdat->hDlg = mydial;
}
}

/*
********************************** DIRECTSOUND 3D FUNCTIONS *****************************************
*/

/*
** The RLDS3D interface allows you to attach sound(s) to the selected object which can then be played and
** whose 3D audio position will update according to the RL object's position
**
** Some functionality is applied to all sounds, and as a result, the RLDS3D interface keeps a record of
** existing sounds and which frames they are attached to. Removal of a frame and/or a sound updates these
** records
*/

/*
** CALLBACKS - update position as 3D objects move around
*/


// The listener callback is attached to the camera. This is done in CreateScene()
static void CDECL listenerCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delta)
{
arg = arg;
if (!lpDS) return;
D3DVECTOR rlvCameraInfo, rlvCameraUp;

info->camera->GetPosition(info->scene, &rlvCameraInfo);
lp3DListenerInfo->SetPosition(rlvCameraInfo.x, rlvCameraInfo.y,
rlvCameraInfo.z, DS3D_DEFERRED);

info->camera->GetOrientation(info->scene, &rlvCameraInfo, &rlvCameraUp);
lp3DListenerInfo->SetOrientation(rlvCameraInfo.x, rlvCameraInfo.y,
rlvCameraInfo.z, rlvCameraUp.x,
rlvCameraUp.y, rlvCameraUp.z, DS3D_DEFERRED);

info->camera->GetVelocity(info->scene, &rlvCameraInfo, TRUE);
lp3DListenerInfo->SetVelocity(rlvCameraInfo.x, rlvCameraInfo.y,
rlvCameraInfo.z, DS3D_DEFERRED);

lp3DListenerInfo->CommitDeferredSettings();
}


// Sounds are updated whenever their frame moves using Render Callbacks. This is the callback function.
static void CDECL soundCallback(LPDIRECT3DRMFRAME obj, void* arg, D3DVALUE delay)
{
arg = arg;
LPDIRECT3DRMFRAME tFrame = (LPDIRECT3DRMFRAME)obj;
if (!lpDS) return;
// Get the sound from the frame's app data
LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)tFrame->GetAppData();
if (!lpAppDat) return;
// Get the 3D sound buffer and remove the sound callback if it's NULL since it shouldn't exist
if (!lpAppDat->Sound || !lpAppDat->i3DSound) {
releaseSoundCallback(tFrame);
return;
}
D3DVECTOR rlvVisualInfo, rlvVisualUp;

tFrame->GetPosition(info->scene, &rlvVisualInfo);
lpAppDat->i3DSound->SetPosition(rlvVisualInfo.x, rlvVisualInfo.y,
rlvVisualInfo.z, DS3D_DEFERRED);

tFrame->GetOrientation(info->scene, &rlvVisualInfo, &rlvVisualUp);
lpAppDat->i3DSound->SetConeOrientation(rlvVisualInfo.x, rlvVisualInfo.y,
rlvVisualInfo.z, DS3D_DEFERRED);

tFrame->GetVelocity(info->scene, &rlvVisualInfo, TRUE);
lpAppDat->i3DSound->SetVelocity(rlvVisualInfo.x, rlvVisualInfo.y,
rlvVisualInfo.z, DS3D_DEFERRED);

lp3DListenerInfo->CommitDeferredSettings();
}

// This adds a sound callback function to a frame
void setSoundCallback(LPDIRECT3DRMFRAME frame) {
frame->AddMoveCallback(soundCallback, NULL);
}

// Removes a sound callback from a frame
void releaseSoundCallback(LPDIRECT3DRMFRAME frame) {
frame->DeleteMoveCallback(soundCallback, NULL);
}

/*
** Sound records - keeps track of which frames have sounds associated with them and
** adds/removes the sounds as required.
*/

struct SoundRecord {
LPDIRECT3DRMFRAME lpFrame;
SoundRecord* next;
};

SoundRecord* top = NULL;

BOOL Recurse_Remove(LPDIRECT3DRMFRAME owner, SoundRecord* record) {
if (!record) return FALSE;
if (Recurse_Remove(owner, record->next)) {
SoundRecord* temp = record->next;
record->next = temp->next;
delete temp;
}
if (owner == record->lpFrame) return TRUE;
return FALSE;
}

// Removes a frame from the records
void RemoveSoundRecord(LPDIRECT3DRMFRAME owner) {
if (!owner) return;

// Don't bother deleting frames without appdata and a sound associated with them
// because they SHOULDN'T be on the list.
if (!owner->GetAppData()) return;
LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(owner->GetAppData());
if (!data->Sound) return;

// Release the sound and remove the soundrecord from the list.
data->i3DSound->Release();
data->i3DSound = NULL;
data->Sound->Release();
data->Sound = NULL;
releaseSoundCallback(owner);

if (Recurse_Remove(owner, top)) {
SoundRecord* temp = top;
top = top->next;
delete temp;
}
}

// Adds a frame to the records
void AddSoundRecord(LPDIRECT3DRMFRAME owner, char* sound_filename) {
if (!lpDS) return;
if (!owner) return;
if (!sound_filename) return;

// Removes all previous sounds from this frame
RemoveSoundRecord(owner);

// Create frame data for this frame if it hasn't already been done
LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(owner->GetAppData());
if (!data) {
data = new FRAMEAPPDATA();
if (!data) return;
owner->SetAppData((ULONG)data);
}

// Create the sound and attach it to the frame
data->Sound = DSLoad3DSoundBuffer(lpDS, sound_filename);
if (!data->Sound) return;

// Query to get the 3D interface, destroy the sound buffer if it's not available...
data->Sound->QueryInterface(IID_IDirectSound3DBuffer, (void**)&data->i3DSound);
if (!data->i3DSound) {
data->Sound->Release();
data->Sound = NULL;
return;
}

// Set the minimum distance at which the sound's amplitude should decay.
data->i3DSound->SetMinDistance((D3DVALUE)10.0, DS3D_IMMEDIATE);

setSoundCallback(owner);

SoundRecord* temp = new SoundRecord;
temp->next = top;
top = temp;
top->lpFrame = owner;
}

/*
** Functionality for users
*/

// Stops all the sounds in the world. (Actually, only in the local RL world.)
// Runs through the records of all the sounds and stops them.
void RLDS3D_StopAllSounds() {
if (!lpDS) return;
SoundRecord* temp = top;
while (temp) {
LPFRAMEAPPDATA data = (LPFRAMEAPPDATA)(temp->lpFrame->GetAppData());
if (data) {
if (data->Sound) data->Sound->Stop();
}
temp = temp->next;
}
}

// Removes all sounds in the world
void RLDS3D_RemoveAllSounds() {
if (!lpDS) return;
while (top) RemoveSoundRecord(top->lpFrame);
UpdateConeVisual();
}

// Plays the sound associated with the currently selected object
void RLDS3D_PlaySound(BOOL bIsLooping) {
if (!sFrame) return;
if (!lpDS) return;
LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)sFrame->GetAppData();
if (lpAppDat) {
if (lpAppDat->Sound) {
lpAppDat->Sound->Stop();
lpAppDat->Sound->SetCurrentPosition(0);
if (bIsLooping) {
lpAppDat->Sound->Play(0,0,DSBPLAY_LOOPING);
}
else {

lpAppDat->Sound->Play(0,0,0); 
}
}
}
}

// Stops the sound associated with the currently selected object
void RLDS3D_StopSelectedSound() {
if (!sFrame) return;
if (!lpDS) return;
LPFRAMEAPPDATA lpAppDat = (LPFRAMEAPPDATA)sFrame->GetAppData();
if (lpAppDat) {
if (lpAppDat->Sound) {
lpAppDat->Sound->Stop();
}
}
}

// Removes the sound from the currently selected object
void RLDS3D_RemoveSound() {
RLDS3D_StopSelectedSound();
if (!sFrame) return;
if (!lpDS) return;
RemoveSoundRecord(sFrame);
UpdateConeVisual();
}

// Attaches a sound (filename provided) to the selected frame
void RLDS3D_AttachSound(char* filename) {
// Removes the sound attached to the currently selected item (if it exists)
RLDS3D_RemoveSound();
if (!sFrame) return;
if (!lpDS) return;
AddSoundRecord(sFrame, filename);
UpdateConeVisual();
}

/*
************************************* MISC. MAINTENANCE FUNCTIONS **************************************
*/

/*
** Allows external users access to the RL Device to deal with Windows-related issues
** (See case WM_ACTIVATE: and case WM_PAINT: in viewer source for examples of HandleActivate() and HandlePaint())
** Design note: This was done to save time from the conversion from the old version of the viewer rather than
** having RLDS3D_HandleActivate(), etc.
*/

LPDIRECT3DRMDEVICE RLDS3D_WinDevice() {
if (!info) return NULL;
return info->dev;
}

/*
** Handles activation messages from the window
*/

void RLDS3D_HandleActivate(WPARAM wparam) {
if (!info || !info->dev) return;
LPDIRECT3DRMWINDEVICE windev;
if (D3DRM_SUCCEED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev))) {
windev->HandleActivate(wparam);
windev->Release();
}
}

/*
** Handles paint messages from the window - the paintstruct which BeginPaint has been called on is
** passed to it
*/

void RLDS3D_HandlePaint(PAINTSTRUCT* ps) {
if (!info) return;
LPDIRECT3DRMWINDEVICE windev;
if (D3DRM_SUCCEED(info->dev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev))) {
windev->HandlePaint(ps->hdc);
windev->Release();
}
}

/*
** Tells whether or not sound is initialized
*/

BOOL RLDS3D_SoundInitialized() {
if (lpDS) return TRUE;
return FALSE;
}

/*
** Tells whether or not a frame is selected
*/

BOOL RLDS3D_FrameSelected() {
if (sFrame) return TRUE;
return FALSE;
}

/*
** Render the scene into the viewport.
*/

void RLDS3D_Render(D3DVALUE time_delta)
{
// When the WM_SIZE message passes 0's as size to the ResizeViewport we know that it's minimized, in which case we don't render it.
if (info->bMinimized == TRUE) return;
D3DRM_SUCCEED(info->scene->Move(time_delta));
D3DRM_SUCCEED(info->view->Clear());
D3DRM_SUCCEED(info->view->Render(info->scene));
D3DRM_SUCCEED(info->dev->Update());
}


/*
* Resize the viewport and device when the window size changes.
*/
void RLDS3D_ResizeViewport(int width, int height)
{
int view_width = info->view->GetWidth();
int view_height = info->view->GetHeight();
int dev_width = info->dev->GetWidth();
int dev_height = info->dev->GetHeight();

if (!(width && height)) {
info->bMinimized = TRUE;
return;

}
else info->bMinimized = FALSE;

if (view_width == width && view_height == height)
return;
else info->bMinimized = FALSE;

if (width <= dev_width && height <= dev_height) {
info->view->Release();
D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, width, height, &info->view));
info->view->SetBack(D3DVAL(400.0));
}

int ret;
if (!RebuildDevice(hwndParent, info, width, height)) {
ret = MessageBox(hwndParent, "Unable to create Direct3D device", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
PostMessage(hwndParent, WM_CLOSE,0,0);
};
}

/*
** Sets/Gets the polygon fill mode
*/

D3DRMFILLMODE RLDS3D_GetPolygonFillMode(void) {
return (D3DRMFILLMODE)(info->dev->GetQuality() & D3DRMFILL_MASK);
}

void RLDS3D_SetPolygonFillMode(D3DRMFILLMODE quality) {
D3DRMRENDERQUALITY oldq = info->dev->GetQuality();
oldq = (oldq & ~D3DRMFILL_MASK) | quality;
info->dev->SetQuality(oldq);
}

/*
** Sets/Gets the polygon shading mode
*/

D3DRMSHADEMODE RLDS3D_GetPolygonShadeMode(void) {
return (D3DRMSHADEMODE)(info->dev->GetQuality() & D3DRMSHADE_MASK);
}

void RLDS3D_SetPolygonShadeMode(D3DRMSHADEMODE quality) {
D3DRMRENDERQUALITY oldq = info->dev->GetQuality();
oldq = (oldq & ~D3DRMSHADE_MASK) | quality;
info->dev->SetQuality(oldq);
}


/*
** Sets/Gets the color model for the viewport (RGB or mono (256-color-based))
*/

D3DRMCOLORMODEL RLDS3D_GetColourModel(void) {
return info->model;
}

void RLDS3D_SetColourModel(D3DRMCOLORMODEL model) {
info->model = model;
int ret;
if (!RebuildDevice(hwndParent, info, info->dev->GetWidth(), info->dev->GetHeight())) {
ret = MessageBox(hwndParent, "Unable to selected Direct3D device", "D3DRM Fatal Error", MB_APPLMODAL|MB_ICONSTOP|MB_OK);
PostMessage(hwndParent, WM_CLOSE,0,0);
}
}

/*
** Sets/Gets whether or not lighting is on
*/

BOOL RLDS3D_GetLighting(void) {
D3DRMLIGHTMODE mode = (D3DRMLIGHTMODE)(info->dev->GetQuality() & D3DRMLIGHT_MASK);
if (mode == D3DRMLIGHT_ON) return TRUE;
return FALSE;
}

void RLDS3D_SetLighting(BOOL new_val) {
D3DRMRENDERQUALITY qual = info->dev->GetQuality() & ~D3DRMLIGHT_MASK;
if (new_val) qual |= D3DRMLIGHT_ON; else qual |= D3DRMLIGHT_OFF;
info->dev->SetQuality(qual);
}

/*
** Sets/Gets whether or not dithering is on
*/

BOOL RLDS3D_GetDither(void) {
return info->dev->GetDither();
}

void RLDS3D_SetDither(BOOL dither) {
info->dev->SetDither(dither);
}

/*
** Sets/Gets texture quality (only relevant for RGB modes)
*/

D3DRMTEXTUREQUALITY RLDS3D_GetTextureQuality(void) {
return info->dev->GetTextureQuality();
}

void RLDS3D_SetTextureQuality(D3DRMTEXTUREQUALITY new_quality) {
info->dev->SetTextureQuality(new_quality);
}


/*
************************************* INTERNAL FUNCTIONS (Not part of API) ********************************
*/

/*
** Given a bounding box this generates a visual representation of it using rods and cones
*/

static LPDIRECT3DRMMESHBUILDER makeBox(D3DRMBOX* box)
{
LPDIRECT3DRMMESHBUILDER mesh;
static D3DVECTOR zero = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
static D3DVECTOR dir = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};
D3DVECTOR a, b;

if (!D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&mesh))) return NULL;

dir.z = box->max.z + D3DVAL(1.0);
AddRod(mesh, D3DVAL(0.05), zero, dir);
a = dir;
a.z += D3DVAL(0.6);
AddCone(mesh, D3DVAL(0.2), dir, a);
a = box->min;
b = a;
b.y = box->max.y;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.x = box->max.x;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.y = box->min.y;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.x = box->min.x;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.z = box->max.z;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.x = box->max.x;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.y = box->max.y;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.x = box->min.x;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b; b.y = box->min.y;
AddRod(mesh, D3DVAL(0.05), a, b);
b.y = box->max.y; a = b; b.z = box->min.z;
AddRod(mesh, D3DVAL(0.05), a, b);
a = b = box->max; b.z = box->min.z;
AddRod(mesh, D3DVAL(0.05), a, b);
a.y = box->min.y; b = a; b.z = box->min.z;
AddRod(mesh, D3DVAL(0.05), a, b);

if (!D3DRM_SUCCEED(mesh->SetColor(D3DRMCreateColorRGB(D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))) {
mesh->Release();
return NULL;
}
return mesh;
}

/*
** Given a box, this creates a mesh in the shape of a 16-sided speaker cone aimed forward with requested angle
*/

#define CONE_POINTS 16
#define pi 3.14159

static LPDIRECT3DRMMESHBUILDER makeSpeaker(D3DRMBOX* box, D3DVALUE in_angle) {
if (!box) return NULL;

D3DVALUE angle = in_angle / D3DVAL(2.0);
DWORD* speaker_faces = new DWORD[CONE_POINTS*4+1];
if (!speaker_faces) return NULL;
memset(speaker_faces, 0, sizeof(DWORD[CONE_POINTS*4+1]));

LPDIRECT3DRMMESHBUILDER mesh;
if (!D3DRM_SUCCEED(lpD3DRM->CreateMeshBuilder(&mesh))) {
delete speaker_faces;
return NULL;
}

static D3DVECTOR zero = {D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)};

D3DVECTOR v[CONE_POINTS+1];

// center of the cone
v[CONE_POINTS] = zero;

int looper;

// Angle along XZ plane which is rotated CONE_POINT times to form cone
D3DVECTOR base_angle;
base_angle.z = (box->max.z + D3DVAL(2.0)) * (D3DVALUE)cos(angle * pi / 180.0);
base_angle.x = (box->max.z + D3DVAL(2.0)) * (D3DVALUE)sin(angle * pi / 180.0);
base_angle.y = D3DVAL(0.0);

for (looper=0; looper<CONE_POINTS; looper++) {
v[looper].z = base_angle.z;
v[looper].x = base_angle.x * (D3DVALUE)cos((looper*2*pi)/CONE_POINTS);
v[looper].y = base_angle.x * (D3DVALUE)sin((looper*2*pi)/CONE_POINTS);
speaker_faces[looper*4] = 3;
speaker_faces[looper*4+1] = looper % CONE_POINTS;
speaker_faces[looper*4+2] = (looper + 1) % CONE_POINTS;
speaker_faces[looper*4+3] = CONE_POINTS;
}

v[CONE_POINTS] = zero;

if (!D3DRM_SUCCEED(mesh->AddFaces(CONE_POINTS+1, v, 0, NULL, speaker_faces, NULL))) {
delete speaker_faces;
mesh->Release();
return NULL;
}

for (looper=0; looper<CONE_POINTS; looper++) {
speaker_faces[looper*4+2] = looper % CONE_POINTS;
speaker_faces[looper*4+1] = (looper + 1) % CONE_POINTS;
}

if (!D3DRM_SUCCEED(mesh->AddFaces(CONE_POINTS+1, v, 0, NULL, speaker_faces, NULL))) {
delete speaker_faces;
mesh->Release();
return NULL;
}

delete speaker_faces;

if (!D3DRM_SUCCEED(mesh->SetColor(D3DRMCreateColorRGB(D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0))))) {
mesh->Release();
return NULL;
}

if (!D3DRM_SUCCEED(mesh->SetQuality((mesh->GetQuality() & ~D3DRMSHADE_MASK) | D3DRMSHADE_FLAT))) {
mesh->Release();
return NULL;
}

return mesh;
}

/*
** Selects the given visual inside the given frame
*/

void UpdateConeVisual(void) {
if (!sFrame) return;
if (!sFrame->GetAppData()) return;
LPFRAMEAPPDATA fd = (LPFRAMEAPPDATA)sFrame->GetAppData();
if (!fd->i3DSound) return;
if (showBoxes && sVisual)
{ D3DRMBOX box;
LPDIRECT3DRMMESHBUILDER builder;
sFrame->DeleteVisual(selectionSpeaker);
sVisual->GetBox(&box);
DWORD temp, outer;
fd->i3DSound->GetConeAngles(&temp, &outer);
builder = makeSpeaker(&box, D3DVAL(temp));
builder->CreateMesh(&selectionSpeaker);
sFrame->AddVisual(selectionSpeaker);
selectionSpeaker->Release();
builder->Release();
}

}

void SelectVisual(LPDIRECT3DRMMESHBUILDER visual, LPDIRECT3DRMFRAME frame) {
RLDS3D_DeselectVisual();
sVisual = visual;
sFrame = frame;

if (sVisual)
{ LPDIRECT3DRMLIGHTARRAY lights;

sLight = 0;
sFrame->GetLights(&lights);
if (lights)
{ if (lights->GetSize())
{ lights->GetElement(0, &sLight);
sLight->Release(); /* reinstate reference count */
}
lights->Release();
}

if (showBoxes && visual)
{ D3DRMBOX box;
LPDIRECT3DRMMESHBUILDER builder;

sVisual->GetBox(&box);
builder = makeBox(&box);
builder->CreateMesh(&selectionBox);
sFrame->AddVisual(selectionBox);
selectionBox->Release();
builder->Release();
UpdateConeVisual();
}
}
}

LPGUID
FindDevice(D3DCOLORMODEL cm)
{
LPDIRECTDRAW lpDD;
LPDIRECT3D lpD3D;
D3DFINDDEVICESEARCH search;
static D3DFINDDEVICERESULT result;
HRESULT error;

if (DirectDrawCreate(NULL, &lpDD, NULL) != DD_OK)
return NULL;

if (lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D) != DD_OK) {
lpDD->Release();
return NULL;
}

memset(&search, 0, sizeof search);
search.dwSize = sizeof search;
search.dwFlags = D3DFDS_COLORMODEL;
search.dcmColorModel = (cm == D3DCOLOR_MONO) ? D3DCOLOR_MONO : D3DCOLOR_RGB;

memset(&result, 0, sizeof result);
result.dwSize = sizeof result;

error = lpD3D->FindDevice(&search, &result);

lpD3D->Release();
lpDD->Release();

if (error != D3D_OK)
return NULL;
else
return &result.guid;
}

/*
* Create the device and viewport.
*/

static BOOL CreateDevice(HWND win, AppInfo* info)
{
RECT r;
int bpp;
HDC hdc;

GetClientRect(win, &r);
if (!D3DRM_SUCCEED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper, NULL, r.right, r.bottom, &info->dev))) return FALSE;

hdc = GetDC(win);
bpp = GetDeviceCaps(hdc, BITSPIXEL);
ReleaseDC(win, hdc);
switch (bpp)
{
case 1:
info->dev->SetShades(4);
lpD3DRM->SetDefaultTextureShades(4);
break;
case 16:
info->dev->SetShades(32);
lpD3DRM->SetDefaultTextureColors(64);
lpD3DRM->SetDefaultTextureShades(32);
info->dev->SetDither(FALSE);
break;
case 24:
info->dev->SetShades(256);
lpD3DRM->SetDefaultTextureColors(64);
lpD3DRM->SetDefaultTextureShades(256);
info->dev->SetDither(FALSE);
break;
default:
info->dev->SetDither(FALSE);
}
if (!CreateScene(info)) {
info->dev->Release();
return FALSE;
}
if (!D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, info->dev->GetWidth(), info->dev->GetHeight(), &info->view))) {
info->dev->Release();
return FALSE;
}
info->view->SetBack(D3DVAL(5000.0));
return TRUE;
}

/*
* Creates a simple scene and adds it to the main scene
*/

static BOOL CreateScene(AppInfo* info)
{
LPDIRECT3DRMFRAME light;
LPDIRECT3DRMLIGHT light1, light2;

// Note that if something fails, we don't bother freeing up everything we've created... the caller to CreateScene should destroy
// the lpD3DRM object and that should happily release everything created with it.
// Also note that, since we're just the viewer, if there's a critical error we pass a quit message with our error message since
// we'd want to quit if the initialize failed anyways...

if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(NULL, &info->scene))) return FALSE;
if (!D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1.0), D3DVAL(1.0), D3DVAL(1.0), &light1))) return FALSE;
if (!D3DRM_SUCCEED(lpD3DRM->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL(0.1), D3DVAL(0.1), D3DVAL(0.1), &light2))) return FALSE;
if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &light))) return FALSE;

light->SetPosition(info->scene, D3DVAL(2.0), D3DVAL(2.0), D3DVAL(5.0));
light->SetOrientation(info->scene, D3DVAL(-1.0), D3DVAL(-1.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0));
light->AddLight(light1);
info->scene->AddLight(light2);

if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &info->camera))) return FALSE;
info->camera->SetPosition(info->scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0));
// Add a callback to the camera's frame so that the listener is updated with the camera
info->camera->AddMoveCallback(listenerCallback, NULL);

light->Release(), light1->Release(), light2->Release();
return TRUE;
}

/*
* Regenerate the device if the color model changes or the window size
* changes.
*/
static BOOL RebuildDevice(HWND win, AppInfo* info, int width, int height)
{
int old_dither = info->dev->GetDither();
D3DRMRENDERQUALITY old_quality = info->dev->GetQuality();
int old_shades = info->dev->GetShades();

info->view->Release();
info->dev->Release();

LPGUID guid = FindDevice(info->model);

if (!guid) return FALSE;

if (!D3DRM_SUCCEED(lpD3DRM->CreateDeviceFromClipper(lpDDClipper, guid, width, height, &info->dev))) return FALSE;

info->dev->SetDither(old_dither);
info->dev->SetQuality(old_quality);
info->dev->SetShades(old_shades);
width = info->dev->GetWidth();
height = info->dev->GetHeight();
if (!D3DRM_SUCCEED(lpD3DRM->CreateViewport(info->dev, info->camera, 0, 0, width, height, &info->view))) return FALSE;
info->view->SetBack(D3DVAL(400.0));
return TRUE;
}

/*
* Place an object in front of the camera.
*/
static void PlaceMesh(LPDIRECT3DRMMESHBUILDER mesh, AppInfo *info)
{
LPDIRECT3DRMFRAME frame;

if (!D3DRM_SUCCEED(lpD3DRM->CreateFrame(info->scene, &frame))) return;
frame->AddVisual(mesh);
frame->SetPosition(info->camera, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15.0));
frame->Release();
}

HRESULT __cdecl loadTextures(char *name, void *arg, LPDIRECT3DRMTEXTURE *tex)
{
char* ext = LSTRRCHR(name, (int)'.');

if (ext && !lstrcmpi(ext, ".ppm"))
if (D3DRM_SUCCEED(lpD3DRM->LoadTexture(name, tex))) return 0;
return -1;
}

/*
** Finds the last occurance of bChar in a null-terminated string, good for finding a pointer to the extension of a filename
*/
char* LSTRRCHR( const char* lpString, int bChar )
{
if( lpString != NULL )
{
const char* lpBegin;

lpBegin = lpString;

while( *lpString != 0 )
{
lpString++;
}

while( 1 )
{
if( *lpString == bChar )
{
return (char*)lpString;
}

if( lpString == lpBegin )
{
break;
}

lpString--;
}
}

return NULL;
} /* LSTRRCHR */


/*
** Strange little function to pick a color from a table using standardized Windows stuff
*/

int ChooseNewColor(HWND win, D3DCOLOR* current)
{
CHOOSECOLOR cc;
COLORREF clr;
COLORREF aclrCust[16];
int i;

for (i = 0; i < 16; i++)
aclrCust[i] = RGB(255, 255, 255);

clr =
RGB
( (int) (255 * D3DRMColorGetRed(*current)),
(int) (255 * D3DRMColorGetGreen(*current)),
(int) (255 * D3DRMColorGetBlue(*current))
);

memset(&cc, 0, sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
cc.hwndOwner = win;
cc.rgbResult = clr;
cc.lpCustColors = aclrCust;
cc.Flags = CC_RGBINIT|CC_FULLOPEN;

if (ChooseColor(&cc))
{ *current =
D3DRMCreateColorRGB
( D3DVAL(GetRValue(cc.rgbResult) / D3DVAL(255.0)),
D3DVAL(GetGValue(cc.rgbResult) / D3DVAL(255.0)),
D3DVAL(GetBValue(cc.rgbResult) / D3DVAL(255.0))
);
return TRUE;
}
else return FALSE;
}