MicroG3D -- A Very Simple Application.
Here is some bare-bones code for getting the engine up and running. This is the most straightforward code I could compose, showing the minimum code needed to look around in a world. It was inspired by ProjectZ, so thanks again Seven.
Features:
Absent Features:
I know you are eager to get started. But first a word of caution...
Getting a first compile CAN TAKE FOREVER when you are a newbie, so please don't give up after the first try! Many users are newcomers to C/C++ (as I was), and learning to deal with the compiler can be frustrating. We have all been there, but remember that patience pays off. Setting up your compiler project is critical. I use Borland's C++ Builder, but the same principles should apply with VC++. You have to have a PROJECT FILE that includes all sorts of useful information for the compiler to use. It should include at least the following:
Then, after you get the program compiled, you are not quite ready for it to run yet. You have to make sure that your program has access to some important .dll files. You will need the Genesis.dll file in the directory where your program is running. And also the video driver files. For this demo, you will need D3DDrv.dll in the same directory (and DirectX must be installed on your computer). I then typically have two subdirectories for levels and actors. Thus my directory looks like this:
[MicroG3D]
+--[Actors]
+--dema.act
+--[Levels]
+--MyLevel.bsp
+--MicroG3d.exe
+--D3DDrv.dll
+--Genesis.dll
+--(my source and project files)
A few important notes:
OK, now go get started!
//---------------------------------------------------------
//Start of Code
//---------------------------------------------------------
//MicroG3D
//By Kevin Toppenberg, MD (kdtop)
// Completed: 1-16-03
//------------------------------------------------------------------------------------------
#include <windows.h>
#include "genesis.h"
#include <assert.h>
#define APP_NAME "MicroG3d"
//--------------------------------------------------------------------
HWND GetOrMakeWindowHandle(HINSTANCE hInstance, int CmdShow, int& Width, int& Height)
{
WNDCLASSEX wcex;
RECT ScreenRect;
int WinXPos, WinYPos;
HWND hWnd = 0;
RECT r;
// create a nice looking window and register it
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DefWindowProc; //HandleMessagesProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = APP_NAME;
wcex.hIconSm = NULL;
RegisterClassEx(&wcex);
// store the window handle in the class variable
hWnd = CreateWindow(APP_NAME, APP_NAME, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, Width, Height, NULL, NULL, hInstance, NULL);
// if something went wrong then bail
assert (hWnd);
// reset the window size to what we want and center it on the screen
GetWindowRect (GetDesktopWindow(), &ScreenRect);
SetWindowPos (hWnd, HWND_TOP, (((ScreenRect.right + ScreenRect.left) / 2) - (Width / 2)),
(((ScreenRect.bottom + ScreenRect.top - 250) / 2) - (Height / 2)),
Width, Height, SWP_NOCOPYBITS | SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
// let's get started
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
// SetCursor (NULL);
//Recalculate Width and Height
GetClientRect(hWnd,&r);
Width = r.right;
Height = r.bottom;
return (hWnd);
}
void WrapAngles (geFloat* pValue) {
while (*pValue > 2*GE_PI) {*pValue -= 2*GE_PI;}
while (*pValue < 0) {*pValue += 2*GE_PI;}
}
//---------------------------------------------------------------------------
int WINAPI main(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//Declare some more needed variables.
typedef struct tInfo {
geVec3d Pos;
geVec3d Angles;
geXForm3d XForm; // transform that will effect actor or camera movement
geXForm3d PoseXForm;
} tInfo;
geEngine* MyEngine = NULL; // pointer to GENESIS engine structure
geDriver_System* MyDrvSys = NULL; // pointer to GENESIS video driver system structure
geDriver* MyDriver = NULL; // pointer to GENESIS video driver structure
geDriver_Mode* MyMode = NULL; // pointer to GENESIS video mode structure
geWorld* MyWorld = NULL; // pointer to GENESIS World structure
geCamera* MyCamera = NULL; // pointer to GENESIS Lens structure
tInfo MyCameraInfo; // structure to hold camera position and orientation
geVFile* MyFileSys = NULL; // pointer to GENESIS VFile structure
int WindowWidth = 800; // default screen width
int WindowHeight = 400; // default screen height
geRect Rect; // a Rect to store the window size
geActor_Def* ActorDef; // pointer to GENESIS actor definition structure
geActor* MyActor; // pointer to GENESIS actor structure
geFloat ElapsedTime; // Hold elapsed time from last frame render
geExtBox ExtBox; // Holds bounding box for actor
HWND MyWindowHandle; // Handle to output window
//---------------------------------------------------------------------------------------------
//Initialize Engine
//---------------------------------------------------------------------------------------------
//Function to get a handle of a display window.
MyWindowHandle = GetOrMakeWindowHandle(hInstance, nCmdShow, WindowWidth, WindowHeight);
//Make a new Engine
MyEngine = geEngine_Create(MyWindowHandle, APP_NAME, "."); assert (MyEngine);
//Turn off the genesis framerate counter (Optional)
geEngine_EnableFrameRateCounter(MyEngine, GE_FALSE);
//Create the genesis driver system
MyDrvSys = geEngine_GetDriverSystem(MyEngine); assert (MyDrvSys);
//---------------------------------------
//Find desired driver
const char* DriverName;
const char* ModeName;
//cycle through available drivers, looking for Windowed D3D driver
do {
MyDriver = geDriver_SystemGetNextDriver(MyDrvSys, MyDriver);
if (!MyDriver) break;
geDriver_GetName(MyDriver, &DriverName); // get driver name
//Check if this is the DirectX driver
if (strnicmp(DriverName, "(D3D)", 5) !=0) continue;
//Examine available modes for this driver
do {
MyMode = geDriver_GetNextMode(MyDriver, MyMode);
if (!MyMode) break;
geDriver_ModeGetName(MyMode, &ModeName); // get mode name
// try to find a windowed mode
if (strnicmp(ModeName, "Window", 6)==0) break;
} while (MyMode); assert (MyMode);
} while (MyDriver && !MyMode);
assert (MyDriver && MyMode);
//Let the genesis engine intialize with our driver
geEngine_SetDriverAndMode(MyEngine, MyDriver, MyMode);
// brighten things up just a bit (Optional)
geEngine_SetGamma(MyEngine, 2.0f);
//Now make a camera
Rect.Left = 0; Rect.Top = 0; Rect.Right = WindowWidth-1; Rect.Bottom = WindowHeight-1;
MyCamera = geCamera_Create(2.0f, &Rect); assert (MyCamera);
//Create a new File System with info about level
//IMPORTANT NOTE: substitute your desired level path & name below
MyFileSys = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS,
"levels\\MyLevel.bsp", //Put your level filename here
NULL, GE_VFILE_OPEN_READONLY); assert (MyFileSys);
//Create the world
MyWorld = geWorld_Create(MyFileSys); assert(MyWorld);
//Close our file
geVFile_Close(MyFileSys); MyFileSys = NULL;
//Add World to Engine
geEngine_AddWorld(MyEngine, MyWorld);
//Load the actor file.
//IMPORTANT NOTE: substitute your desired actor path & name below
MyFileSys = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS,
"actors\\dema.act", //put name of your actor here
NULL, GE_VFILE_OPEN_READONLY); assert (MyFileSys);
//Create a definition of the actor
ActorDef = geActor_DefCreateFromFile (MyFileSys); assert (ActorDef);
//Create the actual actor
MyActor = geActor_Create(ActorDef); assert (MyActor);
//Add actor to the world
geWorld_AddActor(MyWorld, MyActor, (GE_ACTOR_RENDER_ALWAYS | GE_ACTOR_COLLIDE), 0xffffffff);
//Close our file
geVFile_Close(MyFileSys); MyFileSys = NULL;
//Set the animation to a default value
geActor_ClearPose(MyActor, NULL);
//Get bounding box
assert (geActor_GetDynamicExtBox(MyActor, &ExtBox) == GE_TRUE);
//---------------------------------------------------------------------------------------------
//Engine is now up and running. Now will interact with user, and move actor
//---------------------------------------------------------------------------------------------
LARGE_INTEGER CountsPerSecond, OldQPCCount, CurQPCCount;
DWORD OldCount, CurCount;
BOOL UseQPC; //does hardware supports high-res counter
tInfo MyActorInfo;
geBoolean Running;
geFloat ForwardMagnitude;
geVec3d ForwardVector;
geVec3d tempPos;
GE_Collision CollisionInfo;
geMotion* Motion;
geFloat AnimationTime = 0;
MSG msg; //will hold Windows message info
//Initialize Timing
UseQPC = QueryPerformanceFrequency (&CountsPerSecond); assert (UseQPC);
QueryPerformanceCounter (&OldQPCCount);
//Initialize some variables.
//Note: Depending on the level load, you may need to increase the Y value of .Pos
geVec3d_Set(&MyActorInfo.Pos, 0, 0, 0); //set default values for actor position
geVec3d_Set(&MyActorInfo.Angles, 0, 0, 0); // set default values for actor angles.
Running = GE_TRUE;
//extract the "walk" motion saved in the actor
Motion = geActor_GetMotionByName(ActorDef, "Walk"); assert (Motion);
//---------------------------------------------------------------------------------------------
//Start the main game loop
//---------------------------------------------------------------------------------------------
do {
//Calculate elapsed Time
QueryPerformanceCounter (&CurQPCCount); //get hardward timer count from Windows
ElapsedTime = (float)(CurQPCCount.LowPart-OldQPCCount.LowPart);
ElapsedTime = (float)(ElapsedTime /CountsPerSecond.LowPart);
OldQPCCount = CurQPCCount;
//------------------------------------
//Check for user keyboard input, and respond.
//------------------------------------
ForwardMagnitude = 0; //Default to no movement
if (GetAsyncKeyState(VK_UP)&0x8000) ForwardMagnitude = 1; //positive multiplier -> forward movement
if (GetAsyncKeyState(VK_DOWN)&0x8000) ForwardMagnitude = -1; //negative multiplier -> reverse movement
if (GetAsyncKeyState(VK_LEFT)&0x8000) {MyActorInfo.Angles.Y += 2.0f * ElapsedTime; WrapAngles (&MyActorInfo.Angles.Y);}//rotate left
if (GetAsyncKeyState(VK_RIGHT)&0x8000) {MyActorInfo.Angles.Y -= 2.0f * ElapsedTime; WrapAngles (&MyActorInfo.Angles.Y);}//rotate right
if (GetAsyncKeyState('S')&0x8000) {MyActorInfo.Angles.X += 2.0f * ElapsedTime; WrapAngles (&MyActorInfo.Angles.X);}//rotate down
if (GetAsyncKeyState('W')&0x8000) {MyActorInfo.Angles.X -= 2.0f * ElapsedTime; WrapAngles (&MyActorInfo.Angles.X);}//rotate up
if (GetAsyncKeyState(VK_ESCAPE)&0x8000) Running = GE_FALSE; //quit at end of cycle
//------------------------------------
//Now put our MyActorInfo into an XForm that can move our actor to the desired location/orientation
//------------------------------------
// I have found it helpful to have separate XForms for posing and for movement
// This allows me to do whatever rotations are needed to make the actor look right, while still
// having another XForm that can give an "In" or forward vector that points in a logical direction
geXForm3d_SetIdentity(&(MyActorInfo.XForm)); // clear the actor xform matrix
geXForm3d_SetIdentity(&(MyActorInfo.PoseXForm)); // clear the actor posexform matrix
//First we look up or down (while oriented perpendicular to the X axis)
geXForm3d_RotateX(&MyActorInfo.XForm, MyActorInfo.Angles.X); // remove if you want to keep actor on floor
geXForm3d_RotateX(&MyActorInfo.PoseXForm, -GE_PI/2); //position such that always standing up straight
//Now we can spin left or right on the Y (vertical) axis
geXForm3d_RotateY(&MyActorInfo.XForm, MyActorInfo.Angles.Y);
geXForm3d_RotateY(&MyActorInfo.PoseXForm, MyActorInfo.Angles.Y + GE_PI);
//Note: I don't need or use Z rotation
//get Forward vector, relative to XForm orientation
geXForm3d_GetIn(&MyActorInfo.XForm, &ForwardVector);
//use ForwardVector (scaled for time and movement magnitude) to change Pos, and store in tempPos
// the value 800 is an arbitrary speed factor
geVec3d_AddScaled(&MyActorInfo.Pos, &ForwardVector, 800.0f * ElapsedTime * ForwardMagnitude, &tempPos);
// increment the animation frame
//Note: keeps actor in the walking animation regardless of actual movement--for simplicity
AnimationTime += ElapsedTime; if (AnimationTime > 1.0) AnimationTime = 0;
// check if we hit anything by calling the GENESIS collision function.
// it will search through the World and see if there is any collision with
// anything in the line from Pos to tempPos using the bounding box
// ExtBox.Min to ExtBox.Max which represents our Object minimum holding
// box. if there was any collision, then the Collision structure will hold
// all of the pertinent data about exactly what we hit.
if (geWorld_Collision(MyWorld, &ExtBox.Min, &ExtBox.Max, &MyActorInfo.Pos, &tempPos,
GE_CONTENTS_SOLID_CLIP,GE_COLLIDE_ALL, 0xffffffff, NULL, NULL,
&CollisionInfo)) {
//Collision encountered, so do nothing.
} else {
MyActorInfo.Pos = tempPos;
}
//Now translate the XForms to the actor's position
geXForm3d_Translate(&MyActorInfo.XForm, MyActorInfo.Pos.X, MyActorInfo.Pos.Y, MyActorInfo.Pos.Z);
geXForm3d_Translate(&MyActorInfo.PoseXForm, MyActorInfo.Pos.X, MyActorInfo.Pos.Y, MyActorInfo.Pos.Z);
//set the actor's pose, and position it based on Pose XForm
geActor_SetPose(MyActor, Motion, AnimationTime, &MyActorInfo.PoseXForm);
//------------------------------------
//Now get camera ready to render scene
//------------------------------------
//Set camera to same location as player
MyCameraInfo = MyActorInfo;
//Set up Camera XForm, based on Angles
geXForm3d_SetIdentity(&MyCameraInfo.XForm); // clear out the XForm
geXForm3d_RotateX(&MyCameraInfo.XForm, MyCameraInfo.Angles.X);
geXForm3d_RotateY(&MyCameraInfo.XForm, MyCameraInfo.Angles.Y);
// move the camera to the new location, and raise up to eye level
geXForm3d_Translate(&MyCameraInfo.XForm, MyCameraInfo.Pos.X, MyCameraInfo.Pos.Y+60, MyCameraInfo.Pos.Z);
// lets take a look! (Put the info from XForm into the Camera's internal transform)
geCamera_SetWorldSpaceXForm(MyCamera, &MyCameraInfo.XForm);
//------------------------------------
//Now do actual rendering
//------------------------------------
// clear the back buffer and get ready to render
geEngine_BeginFrame(MyEngine, MyCamera, GE_TRUE);
// render the world onto the back buffer
geEngine_RenderWorld(MyEngine, MyWorld, MyCamera, 0.0f);
// flip the back buffer to the front
geEngine_EndFrame(MyEngine);
//------------------------------------
//------------------------------------
//Check for windows messages
//------------------------------------
while (PeekMessage(&msg, MyWindowHandle, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//------------------------------------
//Hey! We're done for this cycle. Let's do it again!
//------------------------------------
} while (Running);
}
//---------------------------------------------------------
//End of Code
//---------------------------------------------------------