I''m only 14 and I''ve been programming in C++ for a year and I find OpenGL very easy. I''m planning on buying the red book & blue book. I''ve been reading NeHe''s tutorials and they have been helpful. Has anyone been able to cycle through 3 different pictures? By this I mean like in Doom when you wouldn''t play it would cycle through 3 pictures. I modified NeHe''s #6 tutorial to do this but after I hit enter once it skips to the end. Here is my code:
//---------------------------------------------------------------------------
/*
PortalGL Engine Version. 0.00.001(BUILD 120)
By Jarred Capellman
(C)1995-2000 3D Software Inc.
*/
//---------------------------------------------------------------------------
#include // Header File For Windows
#include // Header File For Standard Input/Output
#include // Header File For Variable Argument Routines
#include // Header File For Strings
#include // Header File For Toupper
#include // Header File For File Size
#include // Header File For SetPrecision
#include // Header File For File I/O
#include // Header File For Standard I/O
#include // Header File For Cout
#include // Header File For The OpenGL32 Library
#include // Header File For The GLu32 Library
#include // Header File For The Glaux Library
#include // Header File For Windows Math Library
#include // Header File For The Visual Class Library
USELIB("glaux.lib");
USERES("PortalGL.res");
//---------------------------------------------------------------------------
GLYPHMETRICSFLOAT gmf[256]; // Storage For Information About Our Font
HDC hDC=NULL; // Private GDI Device Context
HGLRC hRC=NULL; // Permanent Rendering Context
HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance; // Holds The Instance Of The Application
GLuint base; // Base Display List For The Font Set
GLfloat rquad; // Angle For The Quad
GLfloat fogColor[1]={1.0f}; // Stores FogColor
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE;// Fullscreen Flag Set To Fullscreen Mode By Default
bool rp; // For Return Key Boolean
int SplashNum=1;
GLuint texture[3]; // Storage For 3 Textures
GLfloat yrot; // Y Rotation
GLfloat zpos; // Z Position
time_t lt;
struct stat statbuf;
struct tm *ptr;
FILE *fp;
#define LOGFILE "GameLog.LOG" //For File I/O
//LightAmbient For Scene Light
//LightDiffuse For Reflected Light Off Objects
//LightPosition For Position Of The Light
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; // Ambient Light Values
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // Diffuse Light Values
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // Light Position
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
FILE *File=NULL; // File Handle
if (!Filename) // Make Sure A Filename Was Given
{
return NULL; // If Not Return NULL
}
File=fopen(Filename,"r"); // Check To See If The File Exists
if (File) // Does The File Exist?
{
fclose(File); // Close The Handle
return auxDIBImageLoad(Filename); // Load The Bitmap And Return A Pointer
}
return NULL; // If Load Failed Return NULL
}
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
int Status=FALSE; // Status Indicator
AUX_RGBImageRec *TextureImage[3]; // Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*3); // Set The Pointer To NULL
// Load The Bitmap, Check For Errors, If Bitmap''s Not Found Quit
if (TextureImage[0]=LoadBMP("logo.bmp"))
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(1, &texture[0]); // Create The Texture
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}
if (TextureImage[0]) // If Texture Exists
{
if (TextureImage[0]->data) // If Texture Image Exists
{
free(TextureImage[0]->data); // Free The Texture Image Memory
}
free(TextureImage[0]); // Free The Image Structure
}
// Load The Bitmap, Check For Errors, If Bitmap''s Not Found Quit
if (TextureImage[1]=LoadBMP("opengl.bmp"))
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(1, &texture[1]); // Create The Texture
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[1]->sizeX, TextureImage[1]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[1]->data);
}
if (TextureImage[1]) // If Texture Exists
{
if (TextureImage[1]->data) // If Texture Image Exists
{
free(TextureImage[1]->data); // Free The Texture Image Memory
}
free(TextureImage[1]); // Free The Image Structure
}
if (TextureImage[2]=LoadBMP("OpenAL.bmp"))
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(1, &texture[2]); // Create The Texture
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[2]->sizeX, TextureImage[2]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[2]->data);
}
if (TextureImage[2]) // If Texture Exists
{
if (TextureImage[2]->data) // If Texture Image Exists
{
free(TextureImage[2]->data); // Free The Texture Image Memory
}
free(TextureImage[2]); // Free The Image Structure
}
return Status; // Return The Status
}
void draw_sphere ()
{
glClearColor(0.0f,0.0f,0.0f,0.5f);
glColor4f(0.0, 0.0, 0.0, 0.0);
glPushMatrix ( );
glRotatef ( 90.0, 1.0, 0.0, 0.0 );
GLUquadricObj* q = gluNewQuadric ( );
gluQuadricDrawStyle ( q, GLU_FILL );
gluQuadricNormals ( q, GLU_SMOOTH );
gluQuadricTexture ( q, GL_TRUE );
gluSphere ( q, 1.0, 20, 20 );
gluDeleteQuadric ( q );
glPopMatrix ( );
zpos=-.5f;
glTranslatef(0.0f,0.0f,zpos);
}
GLvoid KillFont(GLvoid) // Delete The Font
{
glDeleteLists(base, 256); // Delete All 256 Characters
}
GLvoid BuildFont(GLvoid) // Build Our Bitmap Font
{
HFONT font; // Windows Font ID
base = glGenLists(256); // Storage For 256 Characters
font = CreateFont( -12, // Height Of Font
0, // Width Of Font
0, // Angle Of Escapement
0, // Orientation Angle
FW_BOLD, // Font Weight
FALSE, // Italic
FALSE, // Underline
FALSE, // Strikeout
ANSI_CHARSET, // Character Set Identifier
OUT_TT_PRECIS, // Output Precision
CLIP_DEFAULT_PRECIS, // Clipping Precision
ANTIALIASED_QUALITY, // Output Quality
FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch
"Arial"); // Font Name
SelectObject(hDC, font); // Selects The Font We Created
wglUseFontOutlines( hDC, // Select The Current DC
0, // Starting Character
255, // Number Of Display Lists To Build
base, // Starting Display Lists
0.0f, // Deviation From The True Outlines
0.2f, // Font Thickness In The Z Direction
WGL_FONT_POLYGONS, // Polygons, not lines
gmf); // Address Of Buffer To Recieve Data
}
/*
GLvoid glPrint()
A custom console i/o routine
*/
GLvoid glPrint(const char *fmt, ...)
{
float length=0; // Used To Find The Length Of The Text
char text[256]; // Holds Our String
va_list ap; // Pointer To List Of Arguments
if (fmt == NULL) // If There''s No Text
return; // Do Nothing
va_start(ap, fmt); // Parses The String For Variables
vsprintf(text, fmt, ap); // And Converts Symbols To Actual Numbers
va_end(ap); // Results Are Stored In Text
for (unsigned int loop=0;loop<(strlen(text));loop++) // Loop To Find Text Length
{
length+=gmf[text[loop]].gmfCellIncX; // Increase Length By Each Characters Width
}
glTranslatef(-length/2,0.0f,0.0f); // Center Our Text On The Screen
glPushAttrib(GL_LIST_BIT); // Pushes The Display List Bits
glListBase(base); // Sets The Base Character to 0
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // Draws The Display List Text
glPopAttrib(); // Pops The Display List Bits
}
/*
static void GenerateFog()
Generates Fog
*/
static void GenerateFog()
{
GLfloat fog_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; /* white */
glFogi(GL_FOG_MODE, GL_LINEAR);
glFogfv(GL_FOG_COLOR, fog_color);
glFogf(GL_FOG_DENSITY, 0.35f);
glHint(GL_FOG_HINT, GL_NICEST);
glFogf(GL_FOG_START, 1.0f);
glFogf(GL_FOG_END, 4.0f);
glEnable(GL_FOG);
}
/*
GLvoid ReSizeGLScene()
Used To Resize The Screen
*/
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window
{
if (height==0) // Prevent A Divide By Zero By
{
height=1; // Making Height Equal One
}
glViewport(0,0,width,height); // Reset The Current Viewport
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix
}
static void WriteTime()
{
lt = time(NULL);
ptr = localtime(<);
fputs(asctime(ptr), fp);
}
static void WriteLog()
{
char * Version;
if((fp = fopen("GameLog.log", "a+"))==NULL)
{
cout << "Cannot Open GameLog.log, one will be created now...\n";
}
stat(LOGFILE, &statbuf);
glGetString(GL_VENDOR);
glGetString(GL_RENDERER);
Version = "0.00.01 (BUILD 120)";
if (statbuf.st_size == 0)
{
fputs("//Generated By PortalGL Engine Version ", fp);
fputs(Version,fp);
fputs("\n", fp);
fputs("//Do not modify, This file is for troubleshooting errors\n", fp);
fputs("\n", fp);
fprintf(fp,"GL_VERSION: ''%s''\n", glGetString(GL_VERSION));
fprintf(fp,"GL_VENDOR: ''%s''\n", glGetString(GL_VENDOR));
fprintf(fp,"GL_RENDERER: ''%s''\n", glGetString(GL_RENDERER));
WriteTime();
fclose(fp);
}
else
{
fputs("\n", fp);
fprintf(fp,"GL_VERSION: ''%s''\n", glGetString(GL_VERSION));
fprintf(fp,"GL_VENDOR: ''%s''\n", glGetString(GL_VENDOR));
fprintf(fp,"GL_RENDERER: ''%s''\n", glGetString(GL_RENDERER));
WriteTime();
fclose(fp);
}
}
/*
InitGL()
Initilizes OpenGL
*/
int InitGL(GLvoid) // All Of The Setup For OpenGL
{
GLfloat position[] = { 0.5, 0.5, 3.0, 0.0 };
if (!LoadGLTextures()) // Jump To Texture Loading Routine ( NEW )
{
return FALSE; // If Texture Didn''t Load Return FALSE
}
glEnable(GL_TEXTURE_2D); // Enable Texture Mapping ( NEW )
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.5f,0.5f,0.5f,1.0f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glEnable(GL_LINE_SMOOTH); // Anti-Anlansiced Lines // Blend Polygons
glEnable(GL_POLYGON_SMOOTH); // AntiAnlansied Polygons
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // Really Nice Perspective Calculations
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
BuildFont(); // Builds The Font
glColor4f(1.0f,1.0f,1.0f,0.75f); // Full Brightness, 75% Alpha
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Blending Function For Translucency Based On Source Alpha Value
glEnable(GL_BLEND);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT1);
{
GLfloat mat[3] = {0.9745, 0.9175, 0.9175};
glMaterialfv (GL_FRONT, GL_AMBIENT, mat);
mat[0] = 0.61424; mat[1] = 0.4136; mat[2] = 0.4136;
glMaterialfv (GL_FRONT, GL_DIFFUSE, mat);
mat[0] = 0.727811; mat[1] = 0.626959; mat[2] = 0.626959;
glMaterialfv (GL_FRONT, GL_SPECULAR, mat);
glMaterialf (GL_FRONT, GL_SHININESS, 0.6*128.0);
glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); // Setup The Ambient Light
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); // Setup The Diffuse Light
glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.05);
}
WriteLog();
return TRUE; // Initialization Went OK
}
static void DDDSoftwareLogo()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef(0.0f,0.0f,-3.0f);
glRotatef(rquad,0.0f,yrot,0.0f); // Rotate The Quad On The X axis
//draw_sphere();
GenerateFog();
glBindTexture(GL_TEXTURE_2D, texture[0]);
// 3D Software Logo Spinning On A Transparent Rectangular Quadratic
glBegin(GL_QUADS);
// Front Face
glTexCoord2f(0.0f, 0.0f);glVertex3f(-1.0f, -1.0f, 0.0625f);
glTexCoord2f(1.0f, 0.0f);glVertex3f( 1.0f, -1.0f, 0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f( 1.0f, 1.0f, 0.0625f);
glTexCoord2f(0.0f, 1.0f);glVertex3f(-1.0f, 1.0f, 0.0625f);
// Back Face
glTexCoord2f(1.0f, 0.0f);glVertex3f(-1.0f, -1.0f, -0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f(-1.0f, 1.0f, -0.0625f);
glTexCoord2f(0.0f, 1.0f);glVertex3f( 1.0f, 1.0f, -0.0625f);
glTexCoord2f(0.0f, 0.0f);glVertex3f( 1.0f, -1.0f, -0.0625f);
// Top Face
glVertex3f(-1.0f, 1.0f, -0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
// Bottom Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
// Right Face
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
// Left Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, -0.0625f);
glEnd();
yrot+=5.0f;
rquad+=5.0f;
}
static void OpenALLogo()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glClearColor(0.0f,0.0f,0.0f,0.0f);
glTranslatef(0.0f,0.0f,-10.0f);
glBindTexture(GL_TEXTURE_2D, texture[2]);
// 3D Software Logo Spinning On A Transparent Rectangular Quadratic
glBegin(GL_QUADS);
// Front Face
glTexCoord2f(0.0f, 0.0f);glVertex3f(-5.0f, -5.0f, 0.0625f);
glTexCoord2f(1.0f, 0.0f);glVertex3f( 5.0f, -5.0f, 0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f( 5.0f, 5.0f, 0.0625f);
glTexCoord2f(0.0f, 1.0f);glVertex3f(-5.0f, 5.0f, 0.0625f);
// Back Face
glTexCoord2f(0.0f, 0.0f);glVertex3f(-5.0f, -5.0f, -0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f(-5.0f, 5.0f, -0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f( 5.0f, 5.0f, -0.0625f);
glTexCoord2f(0.0f, 0.0f);glVertex3f( 5.0f, -51.0f,-0.0625f);
// Top Face
glVertex3f(-1.0f, 1.0f, -0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
// Bottom Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
// Right Face
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
// Left Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, -0.0625f);
glEnd();
}
static void OpenGLLogo()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glClearColor(0.0f,0.0f,0.0f,0.0f);
glTranslatef(0.0f,0.0f,-10.0f);
glBindTexture(GL_TEXTURE_2D, texture[1]);
// 3D Software Logo Spinning On A Transparent Rectangular Quadratic
glBegin(GL_QUADS);
// Front Face
glTexCoord2f(0.0f, 0.0f);glVertex3f(-5.0f, -5.0f, 0.0625f);
glTexCoord2f(1.0f, 0.0f);glVertex3f( 5.0f, -5.0f, 0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f( 5.0f, 5.0f, 0.0625f);
glTexCoord2f(0.0f, 1.0f);glVertex3f(-5.0f, 5.0f, 0.0625f);
// Back Face
glTexCoord2f(0.0f, 0.0f);glVertex3f(-5.0f, -5.0f, -0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f(-5.0f, 5.0f, -0.0625f);
glTexCoord2f(1.0f, 1.0f);glVertex3f( 5.0f, 5.0f, -0.0625f);
glTexCoord2f(0.0f, 0.0f);glVertex3f( 5.0f, -51.0f,-0.0625f);
// Top Face
glVertex3f(-1.0f, 1.0f, -0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
// Bottom Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
// Right Face
glVertex3f( 1.0f, -1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, -0.0625f);
glVertex3f( 1.0f, 1.0f, 0.0625f);
glVertex3f( 1.0f, -1.0f, 0.0625f);
// Left Face
glVertex3f(-1.0f, -1.0f, -0.0625f);
glVertex3f(-1.0f, -1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, 0.0625f);
glVertex3f(-1.0f, 1.0f, -0.0625f);
glEnd();
}
/*
DrawGLScene()
Draws Objects On The Screen
*/
int DrawGLScene(GLvoid) // Here''s Where We Do All The Drawing
{
if (SplashNum==1)
{
DDDSoftwareLogo();
}
return TRUE; // Everything Went OK
}
/*
GLvoid KillGLWindow()
Terminates The Program
*/
GLvoid KillGLWindow(GLvoid) // Properly Kill The Window
{
if (fullscreen) // Are We In Fullscreen Mode?
{
ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop
ShowCursor(TRUE); // Show Mouse Pointer
}
if (hRC) // Do We Have A Rendering Context?
{
if (!wglMakeCurrent(NULL,NULL)) // Are We Able To Release The DC And RC Contexts?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Set RC To NULL
}
if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Set DC To NULL
}
if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Set hWnd To NULL
}
if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Set hInstance To NULL
}
KillFont(); //Kills The Font
}
/*
BOOL CreateGLWindow
Creats The OpenGL Window
*/
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
GLuint PixelFormat; // Holds The Results After Searching For A Match
WNDCLASS wc; // Windows Class Structure
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0; // Set Left Value To 0
WindowRect.right=(long)width; // Set Right Value To Requested Width
WindowRect.top=(long)0; // Set Top Value To 0
WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height
fullscreen=fullscreenflag; // Set The Global Fullscreen Flag
hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Move, And Own DC For Window
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc Handles Messages
wc.cbClsExtra = 0; // No Extra Window Data
wc.cbWndExtra = 0; // No Extra Window Data
wc.hInstance = hInstance; // Set The Instance
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer
wc.hbrBackground = NULL; // No Background Required For GL
wc.lpszMenuName = NULL; // We Don''t Want A Menu
wc.lpszClassName = "OpenGL"; // Set The Class Name
if (!RegisterClass(&wc)) // Attempt To Register The Window Class
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Exit And Return FALSE
}
if (fullscreen) // Attempt Fullscreen Mode?
{
DEVMODE dmScreenSettings; // Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Makes Sure Memory''s Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth = width; // Selected Screen Width
dmScreenSettings.dmPelsHeight = height; // Selected Screen Height
dmScreenSettings.dmBitsPerPel = bits; // Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
// Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
// If The Mode Fails, Offer Two Options. Quit Or Run In A Window.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
fullscreen=FALSE; // Select Windowed Mode (Fullscreen=FALSE)
}
else
{
// Pop Up A Message Box Letting User Know The Program Is Closing.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // Exit And Return FALSE
}
}
}
if (fullscreen) // Attempt Fullscreen Mode?
{
dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
dwStyle=WS_POPUP; // Windows Style
ShowCursor(FALSE); // Hide Mouse Pointer
}
else
{
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
}
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size
if (!(hWnd=CreateWindowEx( dwExStyle, // Extended Style For The Window
"OpenGL", // Class Name
title, // Window Title
WS_CLIPSIBLINGS | // Required Window Style
WS_CLIPCHILDREN | // Required Window Style
dwStyle, // Selected Window Style
0, 0, // Window Position
WindowRect.right-WindowRect.left, // Calculate Adjusted Window Width
WindowRect.bottom-WindowRect.top, // Calculate Adjusted Window Height
NULL, // No Parent Window
NULL, // No Menu
hInstance, // Instance
NULL))) // Don''t Pass Anything To WM_CREATE
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be
{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
1, // Version Number
PFD_DRAW_TO_WINDOW | // Format Must Support Window
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
PFD_DOUBLEBUFFER, // Must Support Double Buffering
PFD_TYPE_RGBA, // Request An RGBA Format
bits, // Select Our Color Depth
0, 0, 0, 0, 0, 0, // Color Bits Ignored
0, // No Alpha Buffer
0, // Shift Bit Ignored
0, // No Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits Ignored
16, // 16Bit Z-Buffer (Depth Buffer)
0, // No Stencil Buffer
0, // No Auxiliary Buffer
PFD_MAIN_PLANE, // Main Drawing Layer
0, // Reserved
0, 0, 0 // Layer Masks Ignored
};
if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can''t Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can''t Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel Format?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can''t Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get A Rendering Context?
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can''t Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Can''t Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
ShowWindow(hWnd,SW_SHOW); // Show The Window
SetForegroundWindow(hWnd); // Slightly Higher Priority
SetFocus(hWnd); // Sets Keyboard Focus To The Window
ReSizeGLScene(width, height); // Set Up Our Perspective GL Screen
if (!InitGL()) // Initialize Our Newly Created GL Window
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
return TRUE; // Success
}
/*
LRESULT CALLBACK WndProc()
Checks For Windows Error Messages
*/
LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
switch (uMsg) // Check For Windows Messages
{
case WM_ACTIVATE: // Watch For Window Activate Message
{
if (!HIWORD(wParam)) // Check Minimization State
{
active=TRUE; // Program Is Active
}
else
{
active=FALSE; // Program Is No Longer Active
}
return 0; // Return To The Message Loop
}
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
}
break; // Exit
}
case WM_CLOSE: // Did We Receive A Close Message?
{
PostQuitMessage(0); // Send A Quit Message
return 0; // Jump Back
}
case WM_KEYDOWN: // Is A Key Being Held Down?
{
keys[wParam] = TRUE; // If So, Mark It As TRUE
return 0; // Jump Back
}
case WM_KEYUP: // Has A Key Been Released?
{
keys[wParam] = FALSE; // If So, Mark It As FALSE
return 0; // Jump Back
}
case WM_SIZE: // Resize The OpenGL Window
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // Jump Back
}
}
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
/*
int WINAPI WinMain()
Keyboard Routine
*/
int WINAPI WinMain( HINSTANCE hInstance, // Instance
HINSTANCE hPrevInstance, // Previous Instance
LPSTR lpCmdLine, // Command Line Parameters
int nCmdShow) // Window Show State
{
MSG msg; // Windows Message Structure
BOOL done=FALSE; // Bool Variable To Exit Loop
fullscreen=true;
// Create Our OpenGL Window
if (!CreateGLWindow("PortalGL Engine",1280,1024,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
while(!done) // Loop That Runs Until done=TRUE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Updating View Only If Active
{
done=TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1]=FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("PortalGL Engine",1280,1024,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
if (keys[VK_RETURN])
{
SplashNum+=1;
switch(SplashNum)
{
case 2:
OpenGLLogo();
case 3:
OpenALLogo();
}
}
}
}
}
// Shutdown Stuff
KillGLWindow(); // Kill The Window
return (msg.wParam); // Exit The Program
}
OpenGL - How did YOU learn?
First, i'm coding in C.. not C++. But it doesn't matter a lot which language is used.
I spent many weeks and tried different types of code architectures. Most of them were becoming very hard to maintain after a few thousands of lines of code.
Finally, i kept the following one:
I. The source is organized in abstraction layers (ough!). As for now, i have 6 layers. Each layer has its own directoy.
- layer 1 is the lowest abstract level layer. This is where i put the very basic functions and compiler configuration;
- layer 2 features OS independant libraries (such as sockets, strings, files manager, time..)
- layer 3 is the engine's low level mananger. It is OS independant and doesn't use API calls (loading textures, mathematics, etc..)
- layer 4 is the API layer. It can be OS dependant. Actually i got the basic calls to D3D,OpenGL,Direct Draw,Direct Input and Direct Sound. For 3d, it only manipulates sets of triangles.
- layer 5 is the high-level engine (OS/API independant). It works on high-level data structures (octrees, models, objects). Performs visibility, culling, collisions, cameras movement, etc..
- layer 6 is finally the specific game functions. I don't have a lot of code in the layer now
II. The Golden Rule: a module in layer N can only be dependant of a module in a layer M with M<=N. It's quite hard to decide in which layer a module should be. Sometimes i even had to cut a module in two.
Example: the sky. I have a basic sky manager in the layer 3 (for the sky box data); calls to APIs in layer 4 (sending vertices, setting textures, colors); and sky management (generating the textures, moving the clouds/sun) in layer 5. When trying to render() in layer 5, it performs a call to render() in the correct module in layer 4, which uses itself the data in layer 3 to render the sky.
For a given module, i organize it that way:
1. Huge comments on what's the module for, with descriptions of ideas/algorithms/possible optimizations.
2. Included modules.
3. Global variables, if any.
4. Procedures and functions.
Each module gots a .c and .h file. I define the constants, the types and the macros, as well as public prototypes, in the .h file. I also have golden rules of inclusion:
1. Never include a .h file inside a .h file.
2. Never include a .c file inside a .h file.
3. Never include a .c file inside a .c file.
2) and 3) are quite obvious. I know many programmers who do 1) but i avoid it. When i edit a module, i want to see in a single look what are the dependencies, which isn't possible when doing 1). I don't have a single huge .h file. All my modules are lesser than 25k each. Most are around 5k.
Globals: again, i try to avoid them. However, for optimizations, i sometimes use them. It's quite rare. In general, globals are only used in the same module; again, i try to avoid to work on globals of another module. On my 30k lines of codes, i probably do not have more than 30 globals (which is quite low if you ask me..)
Calling conventions: API modules are called l4_|api||modname| (such as l4_glfog.c l4_glframe.c l4_dsoundinit.c). Engine modules are called l5_en|modname| (such as l5_enoctree.c l5_en3dsconverter.c). Functions in a module start with |API/EN|_|modname|_|funcname| (ie, GL_sky_render, EN_octree_create)..
I've been working with the above architecture for 6 months now, and i'm pretty happy with it, to say the least
Y.
Edited by - Ysaneya on July 17, 2000 5:14:27 AM
Edited by - Ysaneya on July 17, 2000 5:15:54 AM
I spent many weeks and tried different types of code architectures. Most of them were becoming very hard to maintain after a few thousands of lines of code.
Finally, i kept the following one:
I. The source is organized in abstraction layers (ough!). As for now, i have 6 layers. Each layer has its own directoy.
- layer 1 is the lowest abstract level layer. This is where i put the very basic functions and compiler configuration;
- layer 2 features OS independant libraries (such as sockets, strings, files manager, time..)
- layer 3 is the engine's low level mananger. It is OS independant and doesn't use API calls (loading textures, mathematics, etc..)
- layer 4 is the API layer. It can be OS dependant. Actually i got the basic calls to D3D,OpenGL,Direct Draw,Direct Input and Direct Sound. For 3d, it only manipulates sets of triangles.
- layer 5 is the high-level engine (OS/API independant). It works on high-level data structures (octrees, models, objects). Performs visibility, culling, collisions, cameras movement, etc..
- layer 6 is finally the specific game functions. I don't have a lot of code in the layer now
II. The Golden Rule: a module in layer N can only be dependant of a module in a layer M with M<=N. It's quite hard to decide in which layer a module should be. Sometimes i even had to cut a module in two.
Example: the sky. I have a basic sky manager in the layer 3 (for the sky box data); calls to APIs in layer 4 (sending vertices, setting textures, colors); and sky management (generating the textures, moving the clouds/sun) in layer 5. When trying to render() in layer 5, it performs a call to render() in the correct module in layer 4, which uses itself the data in layer 3 to render the sky.
For a given module, i organize it that way:
1. Huge comments on what's the module for, with descriptions of ideas/algorithms/possible optimizations.
2. Included modules.
3. Global variables, if any.
4. Procedures and functions.
Each module gots a .c and .h file. I define the constants, the types and the macros, as well as public prototypes, in the .h file. I also have golden rules of inclusion:
1. Never include a .h file inside a .h file.
2. Never include a .c file inside a .h file.
3. Never include a .c file inside a .c file.
2) and 3) are quite obvious. I know many programmers who do 1) but i avoid it. When i edit a module, i want to see in a single look what are the dependencies, which isn't possible when doing 1). I don't have a single huge .h file. All my modules are lesser than 25k each. Most are around 5k.
Globals: again, i try to avoid them. However, for optimizations, i sometimes use them. It's quite rare. In general, globals are only used in the same module; again, i try to avoid to work on globals of another module. On my 30k lines of codes, i probably do not have more than 30 globals (which is quite low if you ask me..)
Calling conventions: API modules are called l4_|api||modname| (such as l4_glfog.c l4_glframe.c l4_dsoundinit.c). Engine modules are called l5_en|modname| (such as l5_enoctree.c l5_en3dsconverter.c). Functions in a module start with |API/EN|_|modname|_|funcname| (ie, GL_sky_render, EN_octree_create)..
I've been working with the above architecture for 6 months now, and i'm pretty happy with it, to say the least
Y.
Edited by - Ysaneya on July 17, 2000 5:14:27 AM
Edited by - Ysaneya on July 17, 2000 5:15:54 AM
Wow! It sounds like you are having a real good experience with your engine! I really wish I had all the C++ knowledge to "think" and plan my own engine. I''m still learning a lot.
What do you think the best way to learn is?
Remember, Software doesn''t have bugs, it just has random features.
What do you think the best way to learn is?
Remember, Software doesn''t have bugs, it just has random features.
Remember, Software doesn''t have bugs, it just has random features.
The best way to learn ? Surely to practise and make mistakes For a more practical advice..i''d say, download a few free 3d engines and study them. The best site i know of is this one:
http://cg.cs.tu-berlin.de/~ki/engines.html
(643 engines listed now). I really liked a little Quake II engine called "poly engine". It has no pretention other than rendering a Q2 level and the source code isn''t that huge. I learnt a lot on this engine.
I''m also playing a lot with Nvidia''s demos and their source code. Though most don''t run on my machine (i have a 3dfx), some do, and even if they don''t, the source code is interesting to see.
And finally, all the tutorials/FAQs i can find on all the topics (lightmaps, shadows, volumetric fog..). The best starting point to search is the official site (www.opengl.org).
Y.
http://cg.cs.tu-berlin.de/~ki/engines.html
(643 engines listed now). I really liked a little Quake II engine called "poly engine". It has no pretention other than rendering a Q2 level and the source code isn''t that huge. I learnt a lot on this engine.
I''m also playing a lot with Nvidia''s demos and their source code. Though most don''t run on my machine (i have a 3dfx), some do, and even if they don''t, the source code is interesting to see.
And finally, all the tutorials/FAQs i can find on all the topics (lightmaps, shadows, volumetric fog..). The best starting point to search is the official site (www.opengl.org).
Y.
July 18, 2000 12:50 PM
i learn in the back seat of my dads station wagon. 45 years ago when opengl wasn''t called opengl it was called screw the pooch
I got started on the Program Ported to Win32 example that came with the BCB 3 Standard (a part of the MS documentation)
joeG
joeG
joeG
I started learning C by taking a class at foothill college, then the next year i took a c++ class. From then on i taught myself c++ and learned opengl solely from good old Nehe. I used a win98 programming book to help me. I learned directx from sams teach yourself directx7 in 24 hours...and i also learned game theory from it like main game loop and timers. Now this summer I am taking an advanced c++ class at deanza. I have started a major game now...with the help of my friends...i am doing all the coding and managing, one friend is handling all of design and the other is doing art. It is going to be a 3d rpg with a very unique battle system. I am 16 right now...hopefully I finish it before going to college! Cause im just so lazy that i rarely work on it! Oh well my next step is to probably get an opengl book. Also I found everything I have learned so far pretty easy! I don't know what it is but I think I got the natural ability to think like a programmer! By the way...before my C class I had taught myself QBasic with one book on regular basic my dad had and that gorillas game! Good Luck to you all!
Edited by - blide on July 19, 2000 1:31:24 PM
Edited by - blide on July 19, 2000 1:31:24 PM
-blideblide@mail.com
DarkHacker --
Just looking over your code I see something glaringly wrong with your use of keys[VK_ENTER] -- you need to write something to make sure that you''re seeing the key at the cycle it''s first pressed -- not the key even if it''s held down. I think one thing that''s wrong with your code is that the program (which is probably running at 500fps+) is running right through the two cases and you don''t see it because the program''s running so fast.
What I do is at the end of each cycle, after I''ve done all of my key evaluations, I copy all of the elements from keys[] into another array I call lastkeys[]. When I want to test if a key is just being pressed, I use a define I call KeyDown -- it looks like this...
#define KeyDown(x) ((keys[x] && !lastkeys[x]) && keys[x])
This will test that this is the cycle that the key is being pressed for the first time.
I didn''t try compiling your code or anything, but that was the first thing I noticed.
Good luck with your project!
ThomW
www.LMNOpc.com
Just looking over your code I see something glaringly wrong with your use of keys[VK_ENTER] -- you need to write something to make sure that you''re seeing the key at the cycle it''s first pressed -- not the key even if it''s held down. I think one thing that''s wrong with your code is that the program (which is probably running at 500fps+) is running right through the two cases and you don''t see it because the program''s running so fast.
What I do is at the end of each cycle, after I''ve done all of my key evaluations, I copy all of the elements from keys[] into another array I call lastkeys[]. When I want to test if a key is just being pressed, I use a define I call KeyDown -- it looks like this...
#define KeyDown(x) ((keys[x] && !lastkeys[x]) && keys[x])
This will test that this is the cycle that the key is being pressed for the first time.
I didn''t try compiling your code or anything, but that was the first thing I noticed.
Good luck with your project!
ThomW
www.LMNOpc.com
ThomWwww.LMNOpc.com
That 3D RPG sounds really cool! I bet you will have some real fun with that if you make everything dynamic, in regards to updating AREA''s, player spells and skills, level changes, you could make a real fun game for yourself or others to play.
You guys are very talented. The ability to think like a programmer is a gift.. and it shouldn''t be wasted. That is why I am so in to this stuff.. it is what my mind likes doing!
Good luck to all your projects.
-Dragen
Remember, Software doesn''t have bugs, it just has random features.
You guys are very talented. The ability to think like a programmer is a gift.. and it shouldn''t be wasted. That is why I am so in to this stuff.. it is what my mind likes doing!
Good luck to all your projects.
-Dragen
Remember, Software doesn''t have bugs, it just has random features.
Remember, Software doesn''t have bugs, it just has random features.
quote: Original post by bosco
Apart from that, TBL rocked... The old demo scene rocked and hornet.org rocked.. Ahh .. I wish the demo scene in the U.S. was HALF as active as that in Europe..
In lieu of that, I was hoping to wait until Worthless 2 was done, but if anyone reads this and is interested in making "old school" demos, NOT 3D games, drop me a line.. I''d like to start an OpenGL demo group...
Well, you should check out www.scene.org. Everyone download Wonder by Sunflower - it''ll blow your mind.
I''m interested in making real demos with opengl too. But first I''d like to develop an solid opengl engine, which should ease making demos a lot. The next step would be to make a demo system, which would take care of showing the right effect(s) and playing the right music at the right time.
Well, bosco, if you''re interested, drop me a line. Anyone else interested in demomaking are encouraged to email me too
Jaakko
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement