www.pudn.com > 3d_engine.zip > 3DSVIEW.C


/* 
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ 
³                              3DSVIEW                                 ³ 
³                          Panard Vision Demo                          ³ 
³              (C) 1996,97 Olivier Brunet (bruneto@efrei.fr)           ³ 
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ 
*/ 
 
#include  
#include  
#include  
#include  
#include  
 
#include  
#include  
 
#include "pvision.h" 
#include "3dsread.h" 
#include "pmotion.h" 
#include "3dspm.h" 
 
#include "getopt.h" 
 
//-------------------------------------------------------------------------- 
unsigned int Resx=320,Resy=200,depth=8; 
 
PVWorld *Wor; 
PMotion *Anim; 
PVCam   *Cam; 
 
RGBF Emissive={0,0,0}; 
RGBF Diffuse={1,1,1}; 
RGBF Specular={1,0,1}; 
RGBF Ambiant={0,0,0}; 
 
//-------------------------------------------------------------------------- 
SV_devCtx   *DC; 
SV_modeInfo mi; 
 
char DoubleBuffering=TRUE; 
char VblWait=TRUE; 
 
unsigned RenderMode=PVM_PALETIZED8; 
unsigned TV=FALSE; 
unsigned ANIMATION=FALSE; 
float ANIMSPEED=0.5; 
char MatFile[255]="material.def"; 
 
//-------------------------------------------------------------------------- 
 
extern char MyKey(); 
#pragma aux MyKey=\ 
    "in al,60h"\ 
    modify[al]\ 
    value[al]; 
 
/***********************************************************************/ 
 
short VBE_GetModeNumber(ushort *modeList,unsigned xres,unsigned yres, unsigned depth) 
{ 
	ushort		*modes; 
	SV_modeInfo	mi; 
 
		for (modes = modeList; *modes != 0xFFFF; modes++) { 
			if (!SV_getModeInfo(*modes,&mi)) 
				continue; 
            if (mi.BitsPerPixel != depth || mi.XResolution != xres || mi.YResolution != yres) 
				continue; 
                return mi.Mode; 
			} 
            return -1; 
} 
 
void VBE_DisplayModes(ushort *modeList,ushort x,ushort y,ushort d) 
{ 
	ushort		*modes; 
	SV_modeInfo	mi; 
    char tmp[255]; 
 
		for (modes = modeList; *modes != 0xFFFF; modes++) { 
            if (!SV_getModeInfo(*modes,&mi)) continue; 
            if(x!=0) if (mi.XResolution!=x) continue; 
            if(y!=0) if (mi.YResolution!=y) continue; 
            if(d!=0) if (mi.BitsPerPixel!=d) continue; 
            SV_getModeName(tmp,&mi,mi.Mode,TRUE); 
            printf("%s\n",tmp); 
			} 
} 
 
/***********************************************************************/ 
 
 
void PVAPI UserCleanUp(unsigned t) 
{ 
     SV_restoreMode(); 
} 
 
void fatal(char *s) 
{ 
    PV_UserCleanUp(0); 
    printf("\n\nFATAL! : %s\n",s); 
    exit(1); 
} 
 
/***********************************************************************/ 
 
// JPEG 
 
#include  
#include "jpeglib.h" 
 
struct my_error_mgr { 
  struct jpeg_error_mgr pub;	/* "public" fields */ 
 
  jmp_buf setjmp_buffer;	/* for return to caller */ 
}; 
 
typedef struct my_error_mgr * my_error_ptr; 
 
void 
my_error_exit (j_common_ptr cinfo) 
{ 
  my_error_ptr myerr = (my_error_ptr) cinfo->err; 
 
  (*cinfo->err->output_message) (cinfo); 
 
  longjmp(myerr->setjmp_buffer, 1); 
} 
 
 
int 
LoadJpeg (char * filename,PVMaterial *m) 
{ 
  struct jpeg_decompress_struct cinfo; 
  struct my_error_mgr jerr; 
  FILE * infile;		/* source file */ 
  void **buffer; 
  char *buffer2; 
  int row_stride;		/* physical row width in output buffer */ 
  unsigned i; 
 
  printf("Loading %s for %s...\n", filename, m->Name); 
 
  if ((infile = fopen(filename, "rb")) == NULL) { 
    fprintf(stderr, "can't open %s\n", filename); 
    return 0; 
  } 
 
  cinfo.err = jpeg_std_error(&jerr.pub); 
  jerr.pub.error_exit = my_error_exit; 
  if (setjmp(jerr.setjmp_buffer)) { 
    jpeg_destroy_decompress(&cinfo); 
    fclose(infile); 
    return 0; 
  } 
  jpeg_create_decompress(&cinfo); 
 
  jpeg_stdio_src(&cinfo, infile); 
 
  (void) jpeg_read_header(&cinfo, TRUE); 
 
  cinfo.out_color_space=JCS_RGB; 
 
  (void) jpeg_start_decompress(&cinfo); 
 
  row_stride = cinfo.output_width * cinfo.output_components; 
 
  buffer=malloc(cinfo.output_height*sizeof(void*)); 
  if(buffer==NULL) fatal("Not enough memory to load jpeg image.\n"); 
 
  buffer2=malloc(row_stride*cinfo.output_height); 
  if(buffer2==NULL) fatal("Not enough memory to load jpeg image.\n"); 
 
  for(i=0;iName); 
    if ((t=(UPVD8*)malloc(n))==NULL) 
    { 
       printf("Not Enough memory to load %s\n",na); 
       fclose(f); 
       return; 
    } 
    fread(t,1,n,f); 
    fclose(f); 
 
    pal=(RGB*)malloc(sizeof(RGB)*256); 
    memcpy(pal,&t[256*256],256*sizeof(RGB)); 
    PV_SetMaterialTexture(mat,256,256,t,pal); 
} 
 
void LoadBump(char *na,PVMaterial *mat) 
{ 
    UPVD8 *t; 
    FILE *f; 
    unsigned n; 
 
    if ((f=fopen(na,"rb"))==NULL) return; 
    printf("Loading %s for %s's bump map...\n",na,mat->Name); 
    n=filesize(f); 
    if ((t=(UPVD8*)malloc(n))==NULL) fatal("Not Enough memory to load bump map"); 
    fread(t,n,1,f); 
    fclose(f); 
    if(PV_BuildBumpMap(mat,t,256,256,1)!=COOL) fatal("Bump SUUUUUUUXXX");; 
    free(t); 
} 
 
char *ReadLine(FILE *f) 
{ 
    static char b[2048]; 
    unsigned i=0; 
 
    do 
    { 
        fscanf(f,"%s",b); 
    } 
    while(b[0]=='#'); 
 
    while(b[i]!=0) {if(b[i]=='$') b[i]=' ';i++;} 
 
    return b; 
} 
 
int DecodeFlags(char *a2) 
{ 
    int flags=0; 
    if(a2!=NULL) 
            { 
                a2=strupr(a2); 
                if(strcmp("FLAT",a2)==0) flags|=FLAT; 
                if(strcmp("NOTHING",a2)==0) flags|=NOTHING; 
                if(strcmp("GOURAUD",a2)==0) flags|=GOURAUD; 
                if(strcmp("PHONG",a2)==0) flags|=PHONG; 
                if(strcmp("BUMP",a2)==0) flags|=BUMP; 
                if(strcmp("MAPPING",a2)==0) flags|=MAPPING; 
                if(strcmp("AMBIANT_MAPPING",a2)==0) flags|=AMBIANT_MAPPING; 
                if(strcmp("PERSPECTIVE_MAPPING",a2)==0) flags|=PERSPECTIVE_MAPPING; 
                if(strcmp("AUTOMATIC",a2)==0) flags|=AUTOMATIC; 
                if(strcmp("TEXTURE_NONE",a2)==0) flags|=TEXTURE_NONE; 
                if(strcmp("TEXTURE_PALETIZED8",a2)==0) flags|=TEXTURE_PALETIZED8; 
                if(strcmp("TEXTURE_RGB",a2)==0) flags|=TEXTURE_RGB; 
                if(strcmp("TEXTURE_MIPMAP",a2)==0) flags|=TEXTURE_MIPMAP; 
            } 
 
    return flags; 
} 
void LoadMaterialsDefinitions(char *s) 
{ 
    FILE *f; 
    char *l,*a1,*a2; 
    RGBF col,col2,col3,col0; 
    unsigned i,flags1,flags2; 
    PVMaterial *mat; 
 
    if ((f=fopen(s,"rt"))==NULL) return; 
    printf("Reading %s\n",s); 
    while(!feof(f)) 
    { 
        l=ReadLine(f); 
 
        if(strcmp(l,"END")==0) break; 
 
        if(strcmp(l,"SPEED")==0) 
        { 
            l=ReadLine(f); 
            sscanf(l,"%f",&ANIMSPEED); 
        } 
 
        if(strcmp(l,"AMBIANT")==0) 
        { 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.r); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.g); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.b); 
            printf("Setting ambiant light to (%f,%f,%f)\n",col.r,col.g,col.b); 
            PV_WorldSetAmbiantLight(Wor,col); 
        } 
 
        if(strcmp(l,"MATERIAL")==0) 
        { 
            l=ReadLine(f); 
            a1=strdup(l); 
 
            l=ReadLine(f); 
            a2=strtok(l,"|"); 
            flags1=DecodeFlags(a2); 
            while((a2=strtok(NULL,"|"))!=NULL) 
            { 
                flags1|=DecodeFlags(a2); 
            } 
 
            l=ReadLine(f); 
            sscanf(l,"%f",&col0.r); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col0.g); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col0.b); 
 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.r); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.g); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col.b); 
 
            l=ReadLine(f); 
            sscanf(l,"%f",&col2.r); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col2.g); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col2.b); 
 
            l=ReadLine(f); 
            sscanf(l,"%f",&col3.r); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col3.g); 
            l=ReadLine(f); 
            sscanf(l,"%f",&col3.b); 
 
            l=ReadLine(f); 
            sscanf(l,"%u",&i); 
 
            l=ReadLine(f); 
            a2=strtok(l,"|"); 
            flags2=DecodeFlags(a2); 
            while((a2=strtok(NULL,"|"))!=NULL) 
            { 
                flags2|=DecodeFlags(a2); 
            } 
 
            l=ReadLine(f); 
 
            mat=PV_CreateMaterial(a1,flags1,flags2,0); 
            if(mat==NULL) fatal("Not enough memory for materials"); 
            PV_AddMaterial(Wor,mat); 
            PV_SetMaterialLightInfo(mat,col,col2,col3,i); 
            PV_SetMaterialColor(mat,col0.r,col0.g,col0.b); 
            if(strstr(l,".jpg")!=NULL) LoadJpeg(l,mat); else LoadRaw(l, mat); 
 
            l=ReadLine(f); 
            LoadBump(l,mat); 
        } 
 
 
    } 
    fclose(f); 
} 
 
/***********************************************************************/ 
 
void help() 
{ 
    printf("\n\t3DSVIEW [file1.3DS] [file2.3ds] [filex.3ds] [options]\n"); 
    printf("\tex: 3DSVIEW -a zob.3ds -x640 zumbala.3ds -y480 -d -fplouf.def\n"); 
    printf("Options:\n"); 
    printf("\t  -v      disable VBL synch\n"); 
    printf("\t  -d      disable double buffering\n"); 
    printf("\t  -l      list all availlable video modes\n"); 
    printf("\t  -xNNN   where NNN is the horizontal resolution\n"); 
    printf("\t  -yNNN   where NNN is the vertical resolution\n"); 
    printf("\t  -r      use fake 16bits render mode.\n"); 
    printf("\t  -znn    use RGB (nn bits) render mode (currently 15,16,24,32).\n"); 
    printf("\t  -t      TV mode.\n"); 
    printf("\t  -a      Use animation info from .3ds. (MUST be first on the command line)\n"); 
    printf("\t  -ffile  Use file for material definitions.\n"); 
} 
 
char        optionStr[] = "vdlhx:y:rtz:af:"; 
void parseArguments(int argc,char *argv[]) 
/**************************************************************************** 
* 
* Function:     parseArguments 
* Parameters:   argc    - Number of command line arguments 
*               argv    - Array of command line arguments 
* 
* Description:  Parses the command line and forces detection of specific 
*               SuperVGA's if specified. 
* 
****************************************************************************/ 
{ 
	int     i,option; 
	char    *argument; 
 
    /* Parse command line options */ 
 
	i = i; 
	do { 
		option = getopt(argc,argv,optionStr,&argument); 
		switch (option) { 
            case 'v': 
                VblWait=FALSE; 
				break; 
            case 'd': 
                DoubleBuffering=FALSE; 
				break; 
            case 'l': 
                printf("List of available video modes on your machine:\nWARNING: All modes are not supported.\n\n"); 
                printf("8 bits modes :\n"); 
                VBE_DisplayModes(DC->modeList,0,0,8); 
                getch(); 
                printf("\n16 bits modes :\n"); 
                VBE_DisplayModes(DC->modeList,0,0,16); 
                getch(); 
                printf("\n24 bits modes :\n"); 
                VBE_DisplayModes(DC->modeList,0,0,24); 
                getch(); 
                printf("\n32 bits modes :\n"); 
                VBE_DisplayModes(DC->modeList,0,0,32); 
                exit(0); 
				break; 
            case 'x':Resx=atoi(argument);break; 
            case 'y':Resy=atoi(argument);break; 
            case 'r':RenderMode=PVM_RGB16;depth=16;break; 
            case 'z':RenderMode=PVM_RGB; 
                     if(!strcmp(argument,"15")) depth=15; 
                     if(!strcmp(argument,"16")) depth=16; 
                     if(!strcmp(argument,"24")) depth=24; 
                     if(!strcmp(argument,"32")) depth=32; 
                     break; 
            case 't':TV=TRUE;break; 
            case 'a':ANIMATION=TRUE;break; 
            case 'f':strcpy(MatFile,argument);break; 
			case ALLDONE: 
                break; 
 
			case PARAMETER: 
                    printf("Loading %s...\n",argv[nextargv]); 
                    if (LoadMeshFrom3DS(argv[nextargv],Wor)!=COOL) fatal("Unable to load mesh file."); 
                    if(ANIMATION==TRUE) 
                    { 
                        printf("Loading animation info...\n"); 
                        if(LoadAnimFrom3DS(argv[nextargv],Anim,Wor)!=COOL) fatal("Error reading animation file."); 
                    } 
                    argv++; 
                    break; 
            case 'h': 
			case INVALID: 
			default: 
				help(); 
                exit(1); 
			} 
		} while (option != ALLDONE); 
} 
 
/***********************************************************************/ 
 
void main(int argc,char *argv[]) 
{ 
    char *Screen; 
    char Done,ch; 
    PVMesh  *o; 
    PVLight *Light2,*Light1; 
    char change; 
    unsigned i; 
    int page=1; 
    unsigned CumulTime=0,NbTimes=0; 
 
    SV_palette cpal[256]; 
 
    printf("Panard-Vision Demo Program V1.51 (C) 1997, SMKaribou/GMF (bruneto@efrei.fr)\n"); 
    printf("-h for help.\n\n"); 
    printf("Keys : N=Normal Mode, ,=MipMapping, J=Front2Back, F&G=Change field of view.\n"); 
    printf("þ Compiled on %s at %s.\n",__DATE__,__TIME__); 
    printf("þ PMLib (Version %s) compiled on %s at %s.\n",PMOTION_VERSION,PMOTION_DATE,PMOTION_TIME); 
 
    DC = SV_init(FALSE); 
    /*if (!DC || DC->VBEVersion < 0x200) { 
        printf("This program requires a VESA VBE 2.0 or higher compatible SuperVGA. Try\n"); 
		printf("installing the Universal VESA VBE for your video card, or contact your\n"); 
		printf("video card vendor and ask for a suitable TSR\n"); 
		exit(1); 
        } 
    */ 
 
    /*   PVision Init */ 
    InitPVision(); 
    PV_UserCleanUp=UserCleanUp; 
    if((Wor=PV_CreateWorld())==NULL) fatal("Not enough memory for PVWorld."); 
    if((Anim=PM_CreateTree())==NULL) fatal("Not enough memory to create root node for animation.\n"); 
    if((Cam=PV_CreateCam("CAMERA"))==NULL)fatal("Not enough memory to create camera.\n"); 
 
    // Need to create and assign a cam before calling LoadAnimFrom3DS() 
    PV_SetCamFieldOfView(Cam,1.5); 
    Cam->Height=Resy; 
    Cam->Width=Resx; 
    PV_AssignCam(Cam); 
 
    // Analyze command line 
    if (argc<2) 
    { 
        help(); 
        exit(1); 
    } 
    parseArguments(argc,argv); 
 
    // SVGA 
    if(VBE_GetModeNumber(DC->modeList,Resx,Resy,depth)==-1) fatal("Unsupported video mode (try -l option).\n"); 
    SV_getModeInfo(VBE_GetModeNumber(DC->modeList,Resx,Resy,depth),&mi); 
 
    if((mi.Attributes&svHaveMultiBuffer)==0) DoubleBuffering=FALSE; 
    if(((mi.Attributes&svHaveLinearBuffer)==0)&&(DC->haveVirtualBuffer==FALSE)) fatal("Sorry, banked modes not supported.\n"); 
    printf("Rendering in %ux%ux%uc\n\t þ %s\n\t þ %s\n\t þ %s\n",Resx,Resy,depth==32?(1<<24):(1<ReservedColors=0; 
    LoadMaterialsDefinitions(MatFile); 
 
    printf("Thinking....\n"); 
 
    // Go ! Panard Vision 
    PV_SetMode(RenderMode); 
    if(!(PV_Mode&PVM_PALETIZED8)) if(PV_SetRGBIndexingMode(mi.RedMaskSize,mi.GreenMaskSize,mi.BlueMaskSize, 
                            mi.RedFieldPosition,mi.GreenFieldPosition,mi.BlueFieldPosition,mi.RsvdMaskSize)!=COOL) fatal("RGB Setup : Internal Error\n"); 
 
    // The order of the 3 next operations does matter 
    if(PV_CompileMeshes(Wor)!=COOL) fatal("Error during material compilation."); 
    if(PV_CompileMaterials(Wor,NULL,NULL)!=COOL) fatal("Not enough memory for light transcoding maps.\n");; 
    if(Wor->Global256Palette!=NULL) 
        for(i=0;iReservedColors;i++) 
        { 
            Wor->Global256Palette[i].r=i*63/(Wor->ReservedColors-1); 
            Wor->Global256Palette[i].g=i*63/(Wor->ReservedColors-1); 
            Wor->Global256Palette[i].b=i*63/(Wor->ReservedColors-1); 
        } 
 
    // Stats 
    o=Wor->Objs; 
    while(o!=NULL) 
    { 
        printf("\tþ %s (%lu vertexes, %lu faces, %lu boxes), %s\n",o->Name,o->NbrVertexes,o->NbrFaces,o->NbrBoxes,o->Face[0].Material); 
        o=o->Next; 
    } 
    printf("World : %lu vertexes, %lu faces\n",Wor->NbrVertexes,Wor->NbrFaces); 
    printf("%lu objects in the World.\n",Wor->NbrObjs); 
    getch(); 
 
    // SVGA Kit 
    if(!SV_setMode(VBE_GetModeNumber(DC->modeList,Resx,Resy,depth)|svLinearBuffer,FALSE,FALSE,2)) 
       if(!SV_setMode(VBE_GetModeNumber(DC->modeList,Resx,Resy,depth),FALSE,TRUE,2))  fatal("Unable to set up video mode, unable to obtain a frame buffer hardware or software.\n"); 
 
    if(PV_Mode&PVM_PALETIZED8) 
    { 
        for(i=0;i<256;i++) 
        { 
            cpal[i].red=Wor->Global256Palette[i].r*4; 
            cpal[i].green=Wor->Global256Palette[i].g*4; 
            cpal[i].blue=Wor->Global256Palette[i].b*4; 
            cpal[i].alpha=0; 
        } 
        SV_setPalette(0,256,cpal,-1); 
    } 
    if(DoubleBuffering==TRUE)  SV_setActivePage(1); 
 
    /*  Setting up Clipping infos */ 
    if(TV==FALSE) 
    {if (PV_SetClipLimit(0,Resx-1,0,Resy-1,mi.BytesPerScanLine,Resx/2,Resy/2)!=COOL) fatal("Not enough memory for S-Buffer.\n");} 
    else 
    if (PV_SetClipLimit(0,Resx-1,0,Resy/2,mi.BytesPerScanLine*2,Resx/2,Resy/4)!=COOL) fatal("Not enough memory for S-Buffer.\n"); 
 
    // Camera&Light stuff 
    Light2=PV_CreateLight(PVL_INFINITEPOINT,""); 
    PV_SetLightIntensity(Light2,1); 
    PV_SetLightDirection(Light2,0,1,0); 
    PV_AddLight(Wor,Light2); 
 
    Light2=PV_CreateLight(PVL_DIRECTIONAL,""); 
    PV_SetLightIntensity(Light2,1); 
    PV_SetLightDirection(Light2,0,1,0); 
 
    Light1=PV_CreateLight(PVL_DIRECTIONAL,""); 
    PV_SetLightIntensity(Light1,1); 
    PV_SetLightDirection(Light1,0,0,-1); 
 
    Light1->Color.g=0.2; 
    Light1->Color.b=0; 
 
    Light2->Color.r=0.1; 
    Light2->Color.b=0.8; 
 
    PV_AddLight(Wor,Light1); 
    PV_AddLight(Wor,Light2); 
 
    Done=0; 
    ZTimerInit(); 
    do 
    { 
        if(ANIMATION==TRUE){ 
            PM_SetCurrentTime(Anim,Anim->CurrentTime+ANIMSPEED); 
            PM_ComputeCurrentTime(Anim); 
        } 
 
        if(DoubleBuffering==TRUE) 
        { 
            Screen=(char*)DC->originOffset; 
            SV_clear(0); 
        } 
        else memset(Screen,0,mi.BytesPerScanLine*Resy); 
 
        PV_ComputeWorld(Wor); 
        SV_beginDirectAccess(); 
        LZTimerOn(); 
        PV_RenderWorld(Wor,(UPVD8*)Screen); 
        LZTimerOff(); 
        SV_endDirectAccess(); 
        CumulTime+=LZTimerCount(); 
        NbTimes++; 
 
        if((change==1)||(ANIMATION==TRUE)) 
        if(DoubleBuffering==FALSE) 
        { 
            memcpy((void*)DC->originOffset,Screen,mi.BytesPerScanLine*Resy); 
        } 
        else 
        { 
            SV_setVisualPage(page,VblWait); 
            page=1-page; 
            SV_setActivePage(page); 
        } 
 
        if(ANIMATION==FALSE) while(!kbhit()); 
        ch=MyKey(); 
        while(kbhit()) getch(); 
 
        change=1; 
        switch (ch) 
        { 
            // + 
            case 13:PV_ScaleWorld(Wor,1.1,1.1,1.1);break; 
 
            // - 
            case 12:PV_ScaleWorld(Wor,0.9,0.9,0.9);break; 
 
            // 4 
            case 75:PV_SetCamPos(Cam,Cam->pos.xf-1,Cam->pos.yf,Cam->pos.zf); 
                    break; 
 
            // 6 
            case 77:PV_SetCamPos(Cam,Cam->pos.xf+1,Cam->pos.yf,Cam->pos.zf); 
                    break; 
 
            // 2 
            case 80:PV_SetCamPos(Cam,Cam->pos.xf,Cam->pos.yf+1,Cam->pos.zf); 
                    break; 
 
            // 8 
            case 72:PV_SetCamPos(Cam,Cam->pos.xf,Cam->pos.yf-1,Cam->pos.zf); 
                    break; 
 
            // 7 
            case 71:PV_CamAhead(Cam,-1);break; 
 
            // 1 
            case 79:PV_CamAhead(Cam,1);break; 
 
            // A 
            case 30: PV_SetCamAngles(Cam,Cam->yaw,Cam->pitch-0.1,Cam->roll); 
                    break; 
 
            // Q 
            case 16:PV_SetCamAngles(Cam,Cam->yaw,Cam->pitch+0.1,Cam->roll); 
                    break; 
 
            // O 
            case 24:PV_SetCamAngles(Cam,Cam->yaw-0.1,Cam->pitch,Cam->roll); 
                    break; 
 
            // P 
            case 25:PV_SetCamAngles(Cam,Cam->yaw+0.1,Cam->pitch,Cam->roll); 
                    break; 
 
            // Z 
            case 44:PV_SetCamAngles(Cam,Cam->yaw,Cam->pitch,Cam->roll-0.1); 
                    break; 
 
            // S 
            case 31:PV_SetCamAngles(Cam,Cam->yaw,Cam->pitch,Cam->roll+0.1); 
                    break; 
 
            // N 
            case 49:PV_SetMode(RenderMode); 
                    break; 
 
            // M 
            case 50:PV_SetMode(PV_Mode|PVM_MIPMAPPING); 
                    break; 
 
            // X 
            case 45:PV_SetLightDirection(Light1,Light1->Direction.xf+0.1,Light1->Direction.yf,Light1->Direction.zf);break; 
 
            // C 
            case 46:PV_SetLightDirection(Light1,Light1->Direction.xf-0.1,Light1->Direction.yf,Light1->Direction.zf);break; 
 
            // V 
            case 47:PV_SetLightPosition(Light1,Light1->Position.xf+10,Light1->Position.yf,Light1->Position.zf);break; 
 
            // B 
            case 48:PV_SetLightPosition(Light1,Light1->Position.xf-10,Light1->Position.yf,Light1->Position.zf);break; 
 
            // J 
            case 36:PV_SetMode(PV_Mode|PVM_SBUFFER); 
                    break; 
            // F 
            case 33: 
                PV_SetCamFieldOfView(Cam,Cam->fieldofview+0.1);break; 
            // G 
            case 34: 
                PV_SetCamFieldOfView(Cam,Cam->fieldofview-0.1);break; 
 
            // D 
            case 32: 
                Wor->AmbiantLight.b+=0.05;break; 
            // E 
            case 18: 
                Wor->AmbiantLight.b-=0.05;break; 
 
            // ESC 
            case 1:Done=1; 
            default:change=0; 
       } 
    } 
    while(Done==0); 
 
    if(DoubleBuffering==FALSE) free(Screen); 
 
    PV_UserCleanUp(0); 
 
    PV_GarbageCollector(Wor); 
    PV_KillWorld(Wor); 
 
    printf("\n\nSpeed stat: %d frames rendered, %f æs/frame\n",NbTimes,(float)CumulTime/(float)NbTimes); 
    printf("\n\n(C) 1997, SMKaribou"); 
}