/******************************Module*Header*******************************\ * Module Name: sstext3d.c * * Core code for text3D screen saver * * Created: 12-24-94 -by- Marc Fortier [marcfo] * * Copyright (c) 1994 Microsoft Corporation * \**************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define SS_DEBUG 1 #include "sscommon.h" #include "sstext3d.h" #define FMAX_CHORDAL_DEVIATION 0.008f #define FMIN_DEPTH 0.15f #define FMAX_DEPTH 0.6f #define FMIN_VIEW_ANGLE 90.0f #define FMAX_VIEW_ANGLE 130.0f #define FMIN_RANDOM_ANGLE 45.0f #define FMAX_RANDOM_ANGLE 89.0f #define FMIN_SEESAW_ANGLE 63.0f #define FMAX_SEESAW_ANGLE 88.0f #define FMIN_WOBBLE_ANGLE 30.0f #define FMAX_WOBBLE_ANGLE 55.0f #define FMIN_WOBBLE_ANGLE2 40.0f #define FMAX_WOBBLE_ANGLE2 80.0f #define MIN_ROT_STEP 1 #define MAX_ROT_STEP 20 #define FMAX_ZOOM 5.0f // globals static FLOAT gfMinCycleTime = 10.0f; static POINTFLOAT gTrig[360]; // pre-calculated table of sines and cosines static POINTFLOAT gSawTooth[360]; // sawtooth table static POINTFLOAT gInvTrig[360]; // pseudo-inverse trig table static POINT gTrigDif[360]; // table for converting trig->invtrig static POINT gInvTrigDif[360]; // table for converting invtrig->trig AttrContext gac; // Default texture resource TEX_RES gTexRes = { TEX_BMP, IDB_DEFTEX }; typedef struct _LIST *PLIST; typedef struct _LIST { PLIST pnext; PLIST plistComplete; LPTSTR pszStr; } LIST; PLIST gplistComplete = NULL; PLIST gplist = NULL; static void DeleteNameList(); void text3d_Init( void *data ); void text3d_Reset(void *data ); void text3d_Draw(void *data ); void text3d_Reshape(int width, int height, void *data ); void text3d_Finish( void *data ); static void CalcViewParams( AttrContext *pac ); static BOOL InitFont( AttrContext *pac ); static void InitLighting( AttrContext *pac ); static void InitTexture( AttrContext *pac ); static void InitMaterials( AttrContext *pac ); static void InitView( AttrContext *pac ); static FLOAT MapValue( FLOAT fInVal, FLOAT fIn1, FLOAT fIn2, FLOAT fOut1, FLOAT fOut2 ); static int MapValueI( int inVal, int in1, int in2, int out1, int out2 ); static FLOAT CalcChordalDeviation( HDC hdc, AttrContext *pac ); static void (*BoundingBoxProc)( AttrContext *pac); static void CalcBoundingBox( AttrContext *pac ); static void CalcBoundingBoxFromSphere( AttrContext *pac ); static void CalcBoundingBoxFromSpherePlus( AttrContext *pac, FLOAT zmax ); static void CalcBoundingBoxGeneric( AttrContext *pac ); static void CalcBoundingBoxFromExtents( AttrContext *pac, POINT3D *box ); static void CalcBoundingExtent( FLOAT rot, FLOAT x, FLOAT y, POINTFLOAT *extent ); static void SortBoxes( POINT3D *box, FLOAT *boxvp, int numBox ); static void (*GetNextRotProc)( AttrContext *pac ); static void GetNextRotNone( AttrContext *pac ); static void GetNextRotRandom( AttrContext *pac ); static void GetNextRotWobble( AttrContext *pac ); static void InitTrigTable(); static void text3d_UpdateTime( AttrContext *pac, BOOL bCheckBounds ); static void text3d_UpdateString( AttrContext *pac, BOOL bCheckBounds ); static BOOL VerifyString( AttrContext *pac ); static BOOL CheckKeyStrings( LPTSTR testString, PSZ psz ); static void ConvertStringAsciiToUnicode( PSZ psz, PWSTR pwstr, int len ); static void InvertBitsA( char *s, int len ); static void ReadNameList(); static PSZ ReadStringFileA( char *file ); static void CreateRandomList(); static void ResetRotationLimits( AttrContext *pac, int *reset ); static int FrameCalibration( AttrContext *pac, struct _timeb *pBaseTime, int framesPerCycle, int nCycle ); static void SetTransitionPoints( AttrContext *pac, int framesPerCycle, int *trans1, int *trans2, FLOAT *zTrans ); static void AdjustRotationStep( AttrContext *pac, int *reset, POINTFLOAT *oldTrig ); /******************************Public*Routine******************************\ * SetFloaterInfo * * Set the size and motion of the floating window * * ss_SetWindowAspectRatio may be called after this, to finely crop the * window to the text being displayed. But we can't call it here, since this * function is called by common when creating the floating window, before the * text string size has been determined. \**************************************************************************/ static void SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild ) { float sizeFact; float sizeScale; int size; ISIZE *pChildSize = &pChild->size; MOTION_INFO *pMotion = &pChild->motionInfo; AttrContext *pac = &gac; sizeScale = (float)pac->uSize / 100.0f; // range 0..1 sizeFact = 0.25f + (0.5f * sizeScale); // range 25-75% size = (int) (sizeFact * ( ((float)(pParentSize->width + pParentSize->height)) / 2.0f )); SS_CLAMP_TO_RANGE2( size, 0, pParentSize->width ); SS_CLAMP_TO_RANGE2( size, 0, pParentSize->height ); pChildSize->width = pChildSize->height = size; pMotion->posInc.x = .01f * (float) size; if( pMotion->posInc.x < 1.0f ) pMotion->posInc.x = 1.0f; pMotion->posInc.y = pMotion->posInc.x; pMotion->posIncVary.x = .4f * pMotion->posInc.x; pMotion->posIncVary.y = pMotion->posIncVary.x; } /******************************Public*Routine******************************\ * Init * * Initialize - called on first entry into ss. * Called BEFORE gl is initialized! * Just do basic stuff here, like set up callbacks, verify dialog stuff, etc. * * Fills global SSContext structure with required data, and returns ptr * to it. * \**************************************************************************/ SSContext * ss_Init( void ) { // validate some initial dialog settings getIniSettings(); // also called on dialog init // must verify textures here, before GL floater windows are created if( gac.surfStyle == SURFSTYLE_TEX ) { ss_DisableTextureErrorMsgs(); ss_VerifyTextureFile( &gac.texFile ); } // set Init callback ss_InitFunc( text3d_Init ); // set data ptr to be sent with callbacks ss_DataPtr( &gac ); // set configuration info to return gac.ssc.bFloater = TRUE; gac.ssc.floaterInfo.bMotion = TRUE; gac.ssc.floaterInfo.ChildSizeFunc = SetFloaterInfo; gac.ssc.bDoubleBuf = TRUE; gac.ssc.depthType = SS_DEPTH16; return &gac.ssc; } /******************************Public*Routine******************************\ * text3d_Init * * Initializes OpenGL state for text3d screen saver * \**************************************************************************/ void text3d_Init( void *data ) { AttrContext *pac = (AttrContext *) data; // Set any callbacks that require GL ss_UpdateFunc( text3d_Draw ); ss_ReshapeFunc( text3d_Reshape ); ss_FinishFunc( text3d_Finish ); #ifdef SS_DEBUG glClearColor( 0.2f, 0.2f, 0.2f, 0.0f ); #else glClearColor(0.0f, 0.0f, 0.0f, 0.0f); #endif glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); // this sequence must be maintained InitLighting( pac ); InitFont( pac ); InitView( pac ); InitTexture( pac ); InitMaterials( pac ); } /**************************************************************************\ * InitLighting * * Initialize lighting, and back face culling. * \**************************************************************************/ static void InitLighting( AttrContext *pac ) { float ambient1[] = {0.2f, 0.2f, 0.2f, 1.0f}; float ambient2[] = {0.1f, 0.1f, 0.1f, 1.0f}; float diffuse1[] = {0.7f, 0.7f, 0.7f, 1.0f}; float diffuse2[] = {0.7f, 0.7f, 0.7f, 1.0f}; float position1[] = {0.0f, 50.0f, 150.0f, 0.0f}; float position2[] = {25.0f, 150.0f, 50.0f, 0.0f}; float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; glLightfv(GL_LIGHT0, GL_AMBIENT, ambient1); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse1); glLightfv(GL_LIGHT0, GL_POSITION, position1); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT1, GL_AMBIENT, ambient2); glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse2); glLightfv(GL_LIGHT1, GL_POSITION, position2); glEnable(GL_LIGHT1); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glCullFace( GL_BACK ); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); } /**************************************************************************\ * TestFont * * Test that GetOutlineTextMetrics works. If not, wglUseFontOutlines will fail. * * If the font tests bad, delete it and select in the previous one. * \**************************************************************************/ static BOOL TestFont( HFONT hfont ) { OUTLINETEXTMETRIC otm; HFONT hfontOld; HDC hdc = wglGetCurrentDC(); hfontOld = SelectObject(hdc, hfont); if( GetOutlineTextMetrics( hdc, sizeof(otm), &otm) <= 0 ) { SS_DBGPRINT( "sstext3d Init: GetOutlineTextMetrics failure\n" ); SelectObject(hdc, hfontOld); DeleteObject( hfont ); return FALSE; } return TRUE; } /**************************************************************************\ * CreateFont * * Create a true type font and test it * \**************************************************************************/ static HFONT text3d_CreateFont( LOGFONT *plf ) { HFONT hfont; // Create font from LOGFONT data hfont = CreateFontIndirect(plf); if( hfont ) { // Test the font if( ! TestFont( hfont ) ) hfont = (HFONT) 0; } return hfont; } /**************************************************************************\ * InitFont * * \**************************************************************************/ static BOOL InitFont( AttrContext *pac ) { LOGFONT lf; HFONT hfont; int type; float fChordalDeviation; HDC hdc = wglGetCurrentDC(); // Set up the LOGFONT structure memset(&lf, 0, sizeof(LOGFONT)); lstrcpy( lf.lfFaceName, pac->szFontName ); lf.lfWeight = (pac->bBold) ? FW_BOLD : FW_NORMAL; lf.lfItalic = (pac->bItalic) ? (BYTE) 1 : 0; lf.lfHeight = 0; // shouldn't matter lf.lfCharSet = pac->charSet; lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; // Create the font if( ! (hfont = text3d_CreateFont( &lf )) ) { // Couldn't create a true type font with supplied data SS_DBGPRINT( "initial text3d_CreateFont failed: \n" ); } if( !hfont && ss_fOnWin95() ) { // !!! Bug on win95: the font mapper didn't give us anything useful // For some reason GetOutlineTextMetrics fails for some fonts (Symbol) // when using lfHeight = 0 (default height value). lf.lfHeight = -10; if( ! (hfont = text3d_CreateFont( &lf )) ) { SS_DBGPRINT( "text3d_CreateFont with lfHeight != 0 failed: \n" ); } } if( hfont == NULL ) { /* The requested font cannot be loaded. Try to get the system to * load any TrueType font */ hfont = CreateFont( 100, 100, 0, 0, lf.lfWeight, lf.lfItalic, 0, 0, 0, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, NULL ); // If hfont is still null, nothing will be displayed. if( !hfont || !TestFont(hfont) ) { SS_DBGPRINT( "text3d_InitFont failure\n" ); return FALSE; } } // We have a valid font SelectObject(hdc, hfont); // Set extrusion, chordal deviation, and font type #ifdef _PPC_ // !!! Work around for PPC compiler bug // calculate chordalDeviation from input attribute fTesselFact fChordalDeviation = CalcChordalDeviation( hdc, pac ); pac->fDepth = ss_fRand( FMIN_DEPTH, FMAX_DEPTH ); #else pac->fDepth = ss_fRand( FMIN_DEPTH, FMAX_DEPTH ); // calculate chordalDeviation from input attribute fTesselFact fChordalDeviation = CalcChordalDeviation( hdc, pac ); #endif type = pac->surfStyle == SURFSTYLE_WIREFRAME ? WGL_FONT_LINES : WGL_FONT_POLYGONS; // Create a wgl font context if( !(pac->pWglFontC = CreateWglFontContext( hdc, type, pac->fDepth, fChordalDeviation )) ) return FALSE; // intialize the text that will be displayed if( pac->demoType == DEMO_CLOCK ) { text3d_UpdateTime( pac, FALSE ); // sets pac->textXXX params as well } else if( pac->demoType == DEMO_STRING ) { if( !VerifyString( pac ) ) { ConvertStringToList( pac->szText, pac->usText, pac->pWglFontC ); pac->textLen = GetStringExtent( pac->szText, &pac->pfTextExtent, &pac->pfTextOrigin, pac->pWglFontC ); } } return SUCCESS; } /**************************************************************************\ * InitView * * \**************************************************************************/ static void InitView( AttrContext *pac ) { int numRots=0, axis; FLOAT *p3dRotMax = (FLOAT *) &pac->p3dRotMax; FLOAT *p3dRotMin = (FLOAT *) &pac->p3dRotMin; int *ip3dRotStep = (int *) &pac->ip3dRotStep; POINT3D p3d_zero = {0.0f, 0.0f, 0.0f}; int stepRange = 2; // default step range int reset[NUM_AXIS] = {1, 1, 1}; // text is either xmajor or ymajor pac->bXMajor = pac->pfTextExtent.x >= pac->pfTextExtent.y ? TRUE : FALSE; /* At this point, the initial string extents will have been * calculated, and we can use this to determine rotational * characteristics */ // default proc to get next rotation GetNextRotProc = GetNextRotRandom; /* convert the slider speed values to rotation steps, with * a steeper slope at the beginning of the scale */ pac->iRotStep = MapValueI( pac->iSpeed, MIN_SLIDER, MAX_SLIDER, // slider range MIN_ROT_STEP, MAX_ROT_STEP ); // step range // initialize rotation min/max to 0 *( (POINT3D*)p3dRotMin ) = p3d_zero; *( (POINT3D*)p3dRotMax ) = p3d_zero; pac->p3dRot = p3d_zero; /* Set the MAXIMUM rotation limits. This is required initially, in * order to set the bounding box */ switch( pac->rotStyle ) { case ROTSTYLE_NONE: GetNextRotProc = GetNextRotNone; break; case ROTSTYLE_SEESAW: // rotate minor axis if( pac->demoType == DEMO_VSTRING ) // always rotate around y-axis axis = Y_AXIS; else axis = pac->bXMajor ? Y_AXIS : X_AXIS; p3dRotMin[axis] = FMIN_SEESAW_ANGLE; p3dRotMax[axis] = FMAX_SEESAW_ANGLE; break; case ROTSTYLE_WOBBLE: GetNextRotProc = GetNextRotWobble; if( pac->demoType == DEMO_VSTRING ) { axis = Y_AXIS; } else { stepRange = 1; axis = pac->bXMajor ? Y_AXIS : X_AXIS; } p3dRotMin[Z_AXIS] = FMAX_WOBBLE_ANGLE; p3dRotMax[Z_AXIS] = FMAX_WOBBLE_ANGLE; p3dRotMin[axis] = FMIN_WOBBLE_ANGLE2; p3dRotMax[axis] = FMAX_WOBBLE_ANGLE2; break; case ROTSTYLE_RANDOM: // adjust stepRange based on speed stepRange = MapValueI( pac->iSpeed, MIN_SLIDER, (MAX_SLIDER-MIN_SLIDER)/2, 2, 6 ); // step range for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) { p3dRotMin[axis] = FMIN_RANDOM_ANGLE; p3dRotMax[axis] = FMAX_RANDOM_ANGLE; } break; } // set min and max steps pac->iRotMinStep = pac->iRotStep >= (MIN_ROT_STEP + stepRange) ? pac->iRotStep - stepRange : MIN_ROT_STEP; pac->iRotMaxStep = pac->iRotStep + stepRange; // don't limit upper end for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) { ip3dRotStep[axis] = p3dRotMax[axis] != 0.0f ? ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep ) : 0; } // initialize the step iteration pac->ip3dRoti.x = pac->ip3dRoti.y = pac->ip3dRoti.z = 0; // initialize the trig table, for fast rotation calculations InitTrigTable(); // set the current rotation limits pac->p3dRotLimit = *( (POINT3D *)p3dRotMax ); ResetRotationLimits( pac, reset ); // set view angle pac->fFovy = ss_fRand( FMIN_VIEW_ANGLE, FMAX_VIEW_ANGLE ); for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) { if( p3dRotMax[axis] != 0.0f ) numRots++; } // set BoundingBoxProc dependent on which axis are being rotated if( numRots <= 1 ) BoundingBoxProc = CalcBoundingBox; else BoundingBoxProc = CalcBoundingBoxGeneric; (*BoundingBoxProc)( pac ); if( pac->p3dBoundingBox.y == 0.0f ) pac->p3dBoundingBox.y = 1.0f; } /**************************************************************************\ * InitMaterials * * \**************************************************************************/ static void InitMaterials( AttrContext *pac ) { if( pac->bTexture ) { ss_InitTexMaterials(); pac->bMaterialCycle = FALSE; pac->pMat = ss_RandomTexMaterial( TRUE ); } else { ss_InitTeaMaterials(); pac->bMaterialCycle = TRUE; pac->pMat = ss_RandomTeaMaterial( TRUE ); } } /**************************************************************************\ * InitTexture * * History * Apr. 28, 95 : [marcfo] * - Changed texture quality from default to high. * \**************************************************************************/ static void InitTexture( AttrContext *pac ) { if( pac->surfStyle != SURFSTYLE_TEX ) return; // No choice for texture quality in dialog - set to HIGH pac->texQual = TEXQUAL_HIGH; // Try to load the texture file or default texture resource if( ss_LoadTextureFile( &pac->texFile, &pac->texture ) || ss_LoadTextureResource( &gTexRes, &pac->texture ) ) { pac->bTexture = 1; glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); ss_SetTexture( &pac->texture ); // set auto texture coord generation ss_InitAutoTexture( NULL ); } else { // couldn't open .bmp file pac->bTexture = 0; } } /******************************Public*Routine******************************\ * text3d_Finish * * Handles any cleanup on program termination * \**************************************************************************/ void text3d_Finish( void *data ) { AttrContext *pac = (AttrContext *) data; if( pac ) DeleteWglFontContext( pac->pWglFontC ); // delete any name list DeleteNameList(); } /**************************************************************************\ * text3d_Reshape * * - called on resize, expose * - always called on app startup * \**************************************************************************/ void text3d_Reshape(int width, int height, void *data ) { AttrContext *pac = (AttrContext *) data; //mf #if 0 glViewport( 0, 0, width, height ); #endif // calculate new aspect ratio pac->fAspect = height == 0 ? 1.0f : (FLOAT) width / (FLOAT) height; CalcViewParams( pac ); ss_SetWindowAspectRatio( pac->p3dBoundingBox.x / pac->p3dBoundingBox.y ); } /**************************************************************************\ * CalcViewParams * * Calculate viewing parameters, based on window size, bounding box, etc. * \**************************************************************************/ static void CalcViewParams( AttrContext *pac ) { GLdouble zNear, zFar; FLOAT aspectBound, viewDist; FLOAT vHeight; FLOAT fovy; // calculate viewing distance so that front of bounding box within view aspectBound = pac->p3dBoundingBox.x / pac->p3dBoundingBox.y; // this is distance to FRONT of bounding box: viewDist = pac->p3dBoundingBox.y / ( (FLOAT) tan( deg_to_rad(pac->fFovy/2.0f) ) ); // NOTE: these are half-widths and heights if( aspectBound <= pac->fAspect ) { // we are bound by the window's height fovy = pac->fFovy; } else { // we are bound by window's width // adjust fovy, so fovx remains the same vHeight = pac->p3dBoundingBox.x / pac->fAspect; fovy = rad_to_deg( 2.0f * (FLOAT) atan( vHeight / viewDist ) ); } /* Could just use rotation sphere dimensions here, but for now * set clipping planes 10% beyond bounding box. */ zNear = 0.9f * viewDist; zFar = 1.1f * (viewDist + 2.0f*pac->p3dBoundingBox.z); if( pac->demoType == DEMO_VSTRING ) zFar *= FMAX_ZOOM; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective( fovy, pac->fAspect, zNear, zFar ); // set viewing distance so that front of bounding box within view viewDist *= 1.01f; // pull back 1% further to be sure not off by a pixel.. pac->fZtrans = -(viewDist + pac->p3dBoundingBox.z); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef( 0.0f, 0.0f, pac->fZtrans ); } // number of calibration cycles #define MAX_CALIBRATE 2 /**************************************************************************\ * text3d_Draw * * Draw a frame. * \**************************************************************************/ void text3d_Draw( void *data ) { AttrContext *pac = (AttrContext *) data; POINT3D *rot = &pac->p3dRot; static BOOL bCalibrated = FALSE; static int nCycle = 0; // cycle count static int frameCount = 0, maxCount; static int matTransCount, matTransCount2 = 0; static MATERIAL transMat, transMatInc; static FLOAT zTrans, zTransInc; static MATERIAL *pNewMat; static struct _timeb baseTime; static BOOL bInit = FALSE; static int reset[NUM_AXIS] = {1,1,1}; if( !bInit ) { // Do first time init stuff // Start the calibration timer _ftime( &baseTime ); // set default transition points, until calibration done maxCount = 60; SetTransitionPoints( pac, maxCount, &matTransCount, &matTransCount2, &zTrans ); bInit = TRUE; } // take action based on frameCount if( frameCount >= matTransCount ) { // we are in the transition zone if( frameCount == matTransCount ) { // first transition point... // select new material if( pac->bTexture ) pNewMat = ss_RandomTexMaterial( FALSE ); else pNewMat = ss_RandomTeaMaterial( FALSE ); // set material transition, zTrans transition if( pac->demoType == DEMO_VSTRING ) { // transition current material to black ss_CreateMaterialGradient( &transMatInc, pac->pMat, &ss_BlackMat, matTransCount2 - matTransCount ); zTransInc = ((FMAX_ZOOM-1) * pac->fZtrans) / (matTransCount2 - matTransCount); } else { ss_CreateMaterialGradient( &transMatInc, pac->pMat, pNewMat, maxCount - matTransCount ); } // initialize transition values to current settings zTrans = pac->fZtrans; transMat = *(pac->pMat); // begin transition on NEXT frame. } else { // past first transition... if( matTransCount2 && (frameCount == (matTransCount2+1)) ) { // optional second transition point...(only for vstrings) // transition from black to new material ss_CreateMaterialGradient( &transMatInc, &ss_BlackMat, pNewMat, maxCount - matTransCount2 ); // init transition material to black transMat = ss_BlackMat; /* At this point, screen is black, so we can change strings * and resize the floater without any problems. */ if( pac->demoType == DEMO_VSTRING ) text3d_UpdateString( pac, TRUE ); // can cause resize // set zTrans to furthest distance zTrans = (FMAX_ZOOM * pac->fZtrans); zTransInc = (pac->fZtrans - zTrans) / (maxCount - matTransCount2); // change this while string invisible ResetRotationLimits( pac, reset ); } // set the transition material (updates transMat each time) ss_TransitionMaterial( &transMat, &transMatInc ); zTrans += zTransInc; if( frameCount >= maxCount ) { // End of cycle nCycle++; // 1-based // Calibrate on MAX_CALIBRATE cycles if( !bCalibrated && (nCycle >= MAX_CALIBRATE) ) { maxCount = FrameCalibration( pac, &baseTime, maxCount, nCycle ); SetTransitionPoints( pac, maxCount, &matTransCount, &matTransCount2, &zTrans ); bCalibrated = TRUE; } // set, reset stuff pac->pMat = pNewMat; ss_SetMaterial( pNewMat ); zTrans = pac->fZtrans; frameCount = 0; } } } if( pac->demoType == DEMO_CLOCK ) { // have to update the draw string with current time text3d_UpdateTime( pac, TRUE ); } // ok, the string's setup - draw it glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if( pac->demoType == DEMO_VSTRING ) // use zooming zTrans glTranslatef( 0.0f, 0.0f, zTrans ); else // use fixed zTrans glTranslatef( 0.0f, 0.0f, pac->fZtrans ); /* * GetNextRotProc provides sinusoidal rotations */ (*GetNextRotProc)( pac ); // sets pac->p3dRot, or rot if( pac->p3dRotMax.z != 0.0f ) { glRotatef( rot->z, 0.0f, 0.0f, 1.0f ); } if( pac->p3dRotMax.y != 0.0f ) { glRotatef( rot->y, 0.0f, 1.0f, 0.0f ); } if( pac->p3dRotMax.x != 0.0f ) { glRotatef( rot->x, 1.0f, 0.0f, 0.0f ); } glTranslatef( -pac->pfTextOrigin.x - pac->pfTextExtent.x/2.0f, -pac->pfTextOrigin.y + pac->pfTextExtent.y/2.0f, pac->fDepth / 2.0f ); DrawString( pac->usText, pac->textLen, pac->pWglFontC ); glFlush(); frameCount++; } /**************************************************************************\ * SetTransitionPoints * * Calculate draw transition points, as frame count values. * * If doing variable string (VSTRING), first transition point is where we * start fading to black, and 2nd is from black to next material. Also * transition the z translation distance (zTrans). * Note that trans2 indicates the frame number where the image should be * black. The actual transitioning may occur at trans2+1 (see text3d_draw * above). * * For all other cases, set one transition point for fade to next material. * \**************************************************************************/ static void SetTransitionPoints( AttrContext *pac, int framesPerCycle, int *trans1, int *trans2, FLOAT *zTrans ) { *trans1 = (int) (0.5f * (FLOAT) framesPerCycle + 0.5f); if( pac->demoType == DEMO_VSTRING ) { *trans2 = *trans1 + (int) (0.5f * (FLOAT) (framesPerCycle - *trans1) + 0.5f); *zTrans = pac->fZtrans; } } /**************************************************************************\ * text3d_UpdateTime * * Put new time string into the attribute context * \**************************************************************************/ static void text3d_UpdateTime( AttrContext *pac, BOOL bCheckBounds ) { int oldLen; POINTFLOAT textExtent, textOrigin; POINTFLOAT textLowerRight, currentLowerRight; LPTSTR pszLastTime = pac->szText; static TCHAR szNewTime[TEXT_BUF_SIZE] = {0}; GetLocalTime( &(pac->stTime) ); GetTimeFormat( GetUserDefaultLCID(), // locale id 0, // flags &(pac->stTime), // time struct NULL, // format string szNewTime, // buffer TEXT_BUF_SIZE ); // buffer size // Compare new time string with last one if( !lstrcmp( pszLastTime, szNewTime ) ) // time string has not changed, return return; // translate the new time string into display lists in pac->usText ConvertStringToList( szNewTime, pac->usText, pac->pWglFontC ); lstrcpy( pac->szText, szNewTime ); // Check extents of new string // save current values oldLen = pac->textLen; textExtent = pac->pfTextExtent; textOrigin = pac->pfTextOrigin; pac->textLen = GetStringExtent( pac->szText, &textExtent, &textOrigin, pac->pWglFontC ); if( !bCheckBounds ) { // just set new extents and return pac->pfTextExtent = textExtent; pac->pfTextOrigin = textOrigin; return; } /* only update bounding box if new extents are larger, or the number * of chars changes */ bCheckBounds = FALSE; if( pac->textLen != oldLen ) { // recalculate everything bCheckBounds = TRUE; pac->pfTextExtent = textExtent; pac->pfTextOrigin = textOrigin; } else { // accumulate maximum bounding box in pac // calc current lower right limits textLowerRight.x = textOrigin.x + textExtent.x; textLowerRight.y = textOrigin.y - textExtent.y; currentLowerRight.x = pac->pfTextOrigin.x + pac->pfTextExtent.x; currentLowerRight.y = pac->pfTextOrigin.y - pac->pfTextExtent.y; // if new text extents extend beyond current, update if( textOrigin.x < pac->pfTextOrigin.x ) { pac->pfTextOrigin.x = textOrigin.x; bCheckBounds = TRUE; } if( textOrigin.y > pac->pfTextOrigin.y ) { pac->pfTextOrigin.y = textOrigin.y; bCheckBounds = TRUE; } if( textLowerRight.x > currentLowerRight.x ) { pac->pfTextExtent.x = textLowerRight.x - pac->pfTextOrigin.x; bCheckBounds = TRUE; } if( textLowerRight.y < currentLowerRight.y ) { pac->pfTextExtent.y = pac->pfTextOrigin.y - textLowerRight.y; bCheckBounds = TRUE; } } if( bCheckBounds ) { // string size has changed - recalc box and view params (*BoundingBoxProc)( pac ); CalcViewParams( pac ); } } /**************************************************************************\ * text3d_UpdateString * * Select new string to display. * If bCheckBounds, calculate new bounds as well. * \**************************************************************************/ static void text3d_UpdateString( AttrContext *pac, BOOL bCheckBounds ) { static int index = 0; // get next string to display if( gplist == NULL ) CreateRandomList(); lstrcpy( pac->szText, gplist->pszStr ); ConvertStringToList( pac->szText, pac->usText, pac->pWglFontC ); gplist = gplist->pnext; // get new extents pac->textLen = GetStringExtent( pac->szText, &pac->pfTextExtent, &pac->pfTextOrigin, pac->pWglFontC ); if( !bCheckBounds ) return; // calculate bounding box (*BoundingBoxProc)( pac ); if( pac->p3dBoundingBox.y == 0.0f ) // avoid /0 pac->p3dBoundingBox.y = 1.0f; // Make window's aspect ratio dependent on bounding box /* mf: could clear buffer here, so don't get incorrect results on * synchronous resize, but not necessary since we're fading */ ss_SetWindowAspectRatio( pac->p3dBoundingBox.x / pac->p3dBoundingBox.y ); CalcViewParams( pac ); // move window to new random position ss_RandomWindowPos(); } /**************************************************************************\ * GetNextRotWobble * * Calculate next rotation. * - 'step' controls amount of rotation * - rotation values are scaled from -1 to 1 (trig values), and inscribe * circle with r=1 in the zy plane for the ends of the string * - steps for both minor and major rotation axis remain in sync * * History * Apr. 28, 95 : [marcfo] * - Call ResetRotationLimits() when axis rotation is 0 * \**************************************************************************/ static void GetNextRotWobble( AttrContext *pac ) { int *step = (int *) &pac->ip3dRoti; // use step->x int *rotStep = (int *) &pac->ip3dRotStep.z; FLOAT *rotMax = (FLOAT *) &pac->p3dRotMax; POINTFLOAT *pTrig = pac->pTrig; static int resetPoint[NUM_AXIS] = {90,90,0}; // 0 amplitude points int reset[NUM_AXIS] = {0}; // which axis to be reset int axis; pac->p3dRot.z = pac->p3dRotLimit.z * pTrig[ *step ].y; // sin pac->p3dRot.y = pac->p3dRotLimit.y * pTrig[ *step ].x; // cos pac->p3dRot.x = pac->p3dRotLimit.x * pTrig[ *step ].x; // cos // check for 0 amplitude point for non-vstrings for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) { if( rotMax[axis] != 0.0f ) { if( (pac->demoType != DEMO_VSTRING) && (*step == resetPoint[axis]) ) { reset[axis] = 1; ResetRotationLimits( pac, reset ); reset[axis] = 0; } } } // increment step if( (*step += *rotStep) >= 360 ) { // make the step variable *rotStep = ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep ); // start step at variable index *step = ss_iRand( *rotStep ); } } /**************************************************************************\ * GetNextRotRandom * * Same as above, but steps for each axis are not kept in sync * * History : * Apr. 28, 95 : [marcfo] * - Call ResetRotationLimits() when axis rotation is 0 * \**************************************************************************/ static void GetNextRotRandom( AttrContext *pac ) { int *step = (int *) &pac->ip3dRoti; int *rotStep = (int *) &pac->ip3dRotStep; FLOAT *rotMax = (FLOAT *) &pac->p3dRotMax; POINTFLOAT *pTrig = pac->pTrig; static int resetPoint[NUM_AXIS] = {90,90,0}; // 0 amplitude points int reset[NUM_AXIS] = {0}; // which axis to be reset int axis; // set new rotation pac->p3dRot.z = pac->p3dRotLimit.z * pTrig[ step[Z_AXIS] ].y; // sin pac->p3dRot.y = pac->p3dRotLimit.y * pTrig[ step[Y_AXIS] ].x; // cos pac->p3dRot.x = pac->p3dRotLimit.x * pTrig[ step[X_AXIS] ].x; // cos // for each rotation axis... for( axis = X_AXIS; axis < NUM_AXIS; axis++ ) { if( rotMax[axis] != 0.0f ) { // check for 0 amplitude point for non-vstrings if( (pac->demoType != DEMO_VSTRING) && (step[axis] == resetPoint[axis]) ) { reset[axis] = 1; ResetRotationLimits( pac, reset ); reset[axis] = 0; } // increment rotation step and check for end of cycle if( (step[axis] += rotStep[axis]) >= 360 ) { // make the step variable rotStep[axis] = ss_iRand2( pac->iRotMinStep, pac->iRotMaxStep ); // start step at variable index step[axis] = ss_iRand( rotStep[axis] ); } } } } /**************************************************************************\ * GetNextRotNone * * Null rot proc * \**************************************************************************/ static void GetNextRotNone( AttrContext *pac ) { } /**************************************************************************\ * ResetRotationLimits * * Reset the maximum axis rotations. So there won't be too much of a 'jump' * when altering the rotation, this routine should only be called when the * rotation of the specified axis is at zero amplitude. * * Also, change the rotation table if applicable. This affects how the * rotation 'steps' around the axis. * * History : * Apr. 28, 95 : [marcfo] * - Make rotation limits randomnly more extreme * - If trig table switched, call AdjustRotationStep(), to reset the steps * of non-zero axis rotations so that text string doesn't 'jump' * \**************************************************************************/ static void ResetRotationLimits( AttrContext *pac, int *reset ) { FLOAT *p3dRot = (FLOAT *) &pac->p3dRot; // current rotation FLOAT *p3dRotL = (FLOAT *) &pac->p3dRotLimit; // new rot limit FLOAT *p3dRotMin = (FLOAT *) &pac->p3dRotMin; // max rotation FLOAT *p3dRotMax = (FLOAT *) &pac->p3dRotMax; // max rotation POINT3D p3dOldRotL = pac->p3dRotLimit; // save last rot limit POINTFLOAT *oldTrig; int i; // change rotation limits for( i = 0; i < NUM_AXIS; i++ ) { if( p3dRotMax[i] && reset[i] ) { p3dRotL[i] = ss_fRand( p3dRotMin[i], p3dRotMax[i] ); // grossly modify amplitute sometimes for random if( pac->rotStyle == ROTSTYLE_RANDOM ) { if( ss_iRand(10) == 2 ) p3dRotL[i] = ss_fRand( 0.0f, 10.0f ); else if( ss_iRand(10) == 2 ) p3dRotL[i] = ss_fRand( 90.0f, 135.0f ); } } } // change rotation table // use i to set a frequency for choosing gInvTrig table i = 10; oldTrig = pac->pTrig; switch( pac->rotStyle ) { case ROTSTYLE_RANDOM: if( pac->demoType == DEMO_VSTRING ) i = 7; // fall thru... case ROTSTYLE_SEESAW: // Use InvTrig table every now and then if( ss_iRand(i) == 2 ) { pac->pTrig = gInvTrig; } else pac->pTrig = gTrig; break; default: // Always use regular trig table pac->pTrig = gTrig; } // if trig table changed, need to adjust steps of non-zero axis rotations // (otherwise get 'twitch' in rotation) if( pac->pTrig != oldTrig ) { // only deal with axis which didn't have amplitudes modified for( i = 0; i < NUM_AXIS; i++ ) reset[i] = ! reset[i]; AdjustRotationStep( pac, reset, oldTrig ); } } /**************************************************************************\ * AdjustRotationStep * * If trig table is changed in ResetRotationLimits, then axis with non-zero * rotations will appear to jump. This routine modifies the current step * so this will not be apparent. * * History : * Apr. 28, 95 : [marcfo] * - Wrote it * \**************************************************************************/ static void AdjustRotationStep( AttrContext *pac, int *reset, POINTFLOAT *oldTrig ) { int *step = (int *) &pac->ip3dRoti; FLOAT *p3dRotMax = (FLOAT *) &pac->p3dRotMax; int axis; POINT *trigDif; if( pac->demoType == DEMO_VSTRING ) // for now doesn't matter, string is invisible at this point return; // choose diff table to use for modifying step trigDif = (oldTrig == gTrig) ? gTrigDif : gInvTrigDif; for( axis = 0; axis < NUM_AXIS; axis++ ) { if( p3dRotMax[axis] && reset[axis] ) { if( axis != Z_AXIS ) step[axis] += trigDif[ step[axis] ].x; else step[axis] += trigDif[ step[axis] ].y; // check for wrap or out of bounds if( (step[axis] >= 360) || (step[axis] < 0) ) step[axis] = 0; } } } /**************************************************************************\ * FindInvStep * * Finds step in invTrig table with same value as trig table at i * * History : * Apr. 28, 95 : [marcfo] * - Wrote it * \**************************************************************************/ static int FindInvStep( int i ) { FLOAT val, diff, minDiff; int invStep = i; val = gTrig[i].y; invStep = i; minDiff = val - gInvTrig[i].y; while( ++i <= 90 ) { diff = val - gInvTrig[i].y; if( (FLOAT) fabs(diff) < minDiff ) { minDiff = (FLOAT) fabs(diff); invStep = i; } if( diff < 0.0f ) break; } return invStep; } /**************************************************************************\ * FindStep * * Finds step in trig table with same value as invTrig table at i * * History : * Apr. 28, 95 : [marcfo] * - Wrote it * \**************************************************************************/ static int FindStep( int i ) { FLOAT val, diff, minDiff; int step = i; val = gInvTrig[i].y; step = i; minDiff = gTrig[i].y - val; while( --i >= 0 ) { diff = val - gTrig[i].y; if( (FLOAT) fabs(diff) < minDiff ) { minDiff = (FLOAT) fabs(diff); step = i; } if( diff > 0.0f ) break; } return step; } /**************************************************************************\ * InitTrigTable * * Initialize trig look-up tables * * History : * Apr. 28, 95 : [marcfo] * - Calculate 'diff' tables for smooth transitions between trig and * invTrig tables * \**************************************************************************/ static void InitTrigTable() { int i; static int num = 360; FLOAT inc = (2.0f*PI)/((FLOAT)num); // 360 degree range FLOAT angle = 0.0f; int newStep; // calc standard trig table for( i = 0; i < num; i ++ ) { gTrig[i].x = (FLOAT) cos(angle); gTrig[i].y = (FLOAT) sin(angle); angle += inc; } // Calc sawtooth and pseudo-inverse trig table, as well as a diff // table to convert between trig and invTrig. // do y, or sin values first for( i = 0; i <= 90; i ++ ) { gSawTooth[i].y = (int) i / 90.0f; gInvTrig[i].y = 2*gSawTooth[i].y - gTrig[i].y; } // Create tables to convert trig steps to invTrig steps, and vice-versa for( i = 0; i <= 90; i ++ ) { newStep = FindInvStep( i ); gTrigDif[i].y = newStep - i; newStep = FindStep( i ); gInvTrigDif[i].y = newStep - i; // - } // reflect 0-90 to get 90-180 for( i = 1; i <= 90; i ++ ) { gSawTooth[90+i].y = gSawTooth[90-i].y; gInvTrig[90+i].y = gInvTrig[90-i].y; gTrigDif[90+i].y = -gTrigDif[90-i].y; gInvTrigDif[90+i].y = -gInvTrigDif[90-i].y; } // invert 0-180 to get 180-360 for( i = 1; i < 180; i ++ ) { gSawTooth[180+i].y = -gSawTooth[i].y; gInvTrig[180+i].y = -gInvTrig[i].y; gTrigDif[180+i].y = gTrigDif[i].y; gInvTrigDif[180+i].y = gInvTrigDif[i].y; } // calc x, or cos, by phase-shifting y for( i = 0; i < 270; i ++ ) { gSawTooth[i].x = gSawTooth[i+90].y; gInvTrig[i].x = gInvTrig[i+90].y; gTrigDif[i].x = gTrigDif[i+90].y; gInvTrigDif[i].x = gInvTrigDif[i+90].y; } for( i = 0; i < 90; i ++ ) { gSawTooth[i+270].x = gSawTooth[i].y; gInvTrig[i+270].x = gInvTrig[i].y; gTrigDif[i+270].x = gTrigDif[i].y; gInvTrigDif[i+270].x = gInvTrigDif[i].y; } } /**************************************************************************\ * CalcChordalDeviation * * \**************************************************************************/ static FLOAT CalcChordalDeviation( HDC hdc, AttrContext *pac ) { OUTLINETEXTMETRIC otm; FLOAT cd, mincd; // chordal deviations // Query font metrics if( GetOutlineTextMetrics( hdc, sizeof(otm), &otm) <= 0 ) // cmd failed, or buffer size=0 return 1.0f; // minimum chordal deviation is limited by design space mincd = 1.0f / (FLOAT) otm.otmEMSquare; // now map fTesselFact to chordalDeviation cd = MapValue( pac->fTesselFact, 0.0f, 1.0f, // fTesselFact range FMAX_CHORDAL_DEVIATION, mincd ); // chordalDeviation range if( pac->fTesselFact == 0.0f ) // make sure get lowest resolution cd = 1.0f; return cd; } /**************************************************************************\ * CalcBoundingBox * \**************************************************************************/ static void CalcBoundingBox( AttrContext *pac ) { POINT3D box[3]; // for each axis rotation FLOAT viewAngle, critAngle, critAngleC, rectAngle; FLOAT r, rot, x, y, z, xmax, ymax, zCrit; FLOAT viewDist, viewDistO, xAngle[3], angle; FLOAT boxvpo[3]; // viewpoint to origin distance along z for the boxes int n = 0; POINTFLOAT extent; POINT3D pt; /* One thing to remember here is that box[n].z is constrained to be * the near clipping plane. The boxe's x and y represent the frustum * cross-section at that point. */ viewAngle = deg_to_rad( pac->fFovy ) / 2.0f; // x,y,z represent half-extents x = pac->pfTextExtent.x/2.0f; y = pac->pfTextExtent.y/2.0f; z = pac->fDepth/2.0f; // initialize box[0] with current extents box[0].x = x; box[0].y = y; box[0].z = z; boxvpo[0] = 0.0f; // handle rotation around x-axis if( pac->p3dRotMax.x != 0.0f ) { box[n].x = x; // need to determine y and z rot = deg_to_rad( pac->p3dRotMax.x ); r = (FLOAT) sqrt( y*y + z*z ); // calc incursion along z rectAngle = (z == 0.0f) ? PI_OVER_2 : (FLOAT) atan( y/z ); if( rot >= rectAngle ) { // easy, use maximum possible extent box[n].z = r; } else { // rotate lower right corner of box by rot to get extent box[n].z = z * (FLOAT) cos( rot ) + y * (FLOAT) sin( rot ); } /* figure out critical angle, where rotated rectangle would * be perpendicular to viewing frustum. This indicates the max. * y-incursion into the frustum. */ critAngle = PI_OVER_2 - viewAngle; ymax = r * (FLOAT) sin(critAngle); if( y > z ) { rectAngle = PI_OVER_2 - rectAngle; critAngleC = PI_OVER_2 - critAngle; } else critAngleC = critAngle; if( (rectAngle + rot) >= critAngleC ) { // no view reduction possible in y, use max view // need to calc y at box.z viewDistO = r / (FLOAT) cos( critAngle ); boxvpo[n] = viewDistO; box[n].y = (viewDistO - box[n].z) * (FLOAT) tan( PI_OVER_2 - critAngle); } else { // we can sonic reduce it if( y > z ) rot = -rot; // rotate front-top point by rot to get ymax, z ymax = z * (FLOAT) sin( rot ) + y * (FLOAT) cos( rot ); zCrit = z * (FLOAT) cos( rot ) - y * (FLOAT) sin( rot ); // not usin viewDistO properly here... viewDistO = ymax * (FLOAT) tan( viewAngle); boxvpo[n] = viewDistO + zCrit; viewDist = boxvpo[n] - box[n].z; box[n].y = viewDist * (FLOAT) tan( viewAngle ); } n++; } if( pac->p3dRotMax.y != 0.0f ) { box[n].y = y; // need to determine x and z rot = deg_to_rad( pac->p3dRotMax.y ); r = (FLOAT) sqrt( x*x + z*z ); rectAngle = (z == 0.0f) ? PI_OVER_2 : (FLOAT) atan( x/z ); // calc incursion along z if( rot >= rectAngle ) { // easy, use maximum possible extent box[n].z = r; } else { // rotate lower right corner of box by rot to get extent box[n].z = z * (FLOAT) cos( rot ) + x * (FLOAT) sin( rot ); } // view distance to largest z viewDist = y / (FLOAT) tan(viewAngle); // make viewDist represent distance to origin viewDistO = viewDist + box[n].z; boxvpo[n] = viewDistO; // now minimize angle between viewpoint and rotated rect if( viewDistO > r ) { /* calc crit angle where view is maximized (line from viewpoint * tangent to rotation circle) * critAngle is between z-axis and radial line */ critAngle = (FLOAT) acos( r / viewDistO ); // critAngleC is for Comparing if( x > z ) { rectAngle = PI_OVER_2 - rectAngle; critAngleC = PI_OVER_2 - critAngle; } else critAngleC = critAngle; } if( (viewDistO > r) && // vp OUTSIDE circle ((rectAngle + rot) >= critAngleC) ) { /* no view reduction possible in x, use x along the max-view line */ box[n].x = viewDist * (FLOAT) tan( PI_OVER_2 - critAngle ); } else { // we can sonic reduce it if( x > z ) rot = -rot; // rotate front-top point by rot to get x,z // pt.z not needed //pt.z = z * (FLOAT) cos( rot ) - x * (FLOAT) sin( rot ); pt.x = z * (FLOAT) sin( rot ) + x * (FLOAT) cos( rot ); box[n].x = pt.x; } n++; } if( pac->p3dRotMax.z != 0.0f ) { CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.z), x, y, (POINTFLOAT *) &box[n] ); box[n].z = z; // calc viewing distance from front of box viewDist = box[n].y / (FLOAT) tan(viewAngle); // calc view distance to origin; boxvpo[n] = box[n].z + viewDist; n++; } /* XXX!: this is currently only being used for case of one axis rotation * There were clipping problems using it for more axis' */ /* Now we've got 3 rectangles in x-y plane at various depths, and * need to pick the shortest viewing distance that will encompass * all of them. Or, don't actually have to pick the view distance * yet, since might want to wait for the viewport size in Reshape before * we do this - but in that case need to pick the rectangle that 'sticks * out the most', so it can be used as the bounding box. */ /* The box with the furthest viewpoint will work for y. * But then have to check * this against the x's of the others. If any stick out of the frustum, * then it will have to be made larger in x. By making x larger, we * do not affect fovy */ SortBoxes( box, boxvpo, n ); // put largest viewpoint box in box[0] // figure view dist to first box // (could maintain these as they are calculated) viewDist = boxvpo[0] - box[0].z; // compare x angles of boxes switch( n ) { FLOAT den; case 3: den = viewDist + (box[0].z - box[2].z); xAngle[2] = den == 0.0f ? PI_OVER_2 : (FLOAT)atan( box[2].x / den); case 2: den = viewDist + (box[0].z - box[1].z); xAngle[1] = den == 0.0f ? PI_OVER_2 : (FLOAT)atan( box[1].x / den); case 1: xAngle[0] = viewDist == 0.0f ? PI_OVER_2 : (FLOAT)atan( box[0].x / viewDist ); } // here, just call Sort again, with list of xAngles SortBoxes( box, xAngle, n ); // put largest xangle box in box[0] // now box[0] should contain half extents of the Bounding box pac->p3dBoundingBox = box[0]; } /**************************************************************************\ * CalcBoundingBoxFromSphere * * Calculates the bounding box from a sphere with r = diagonal of the box * \**************************************************************************/ static void CalcBoundingBoxFromSphere( AttrContext *pac ) { FLOAT x, y, z, r; FLOAT viewAngle, viewDist, viewDistO; POINT3D box; // x,y,z represent half-extents x = pac->pfTextExtent.x/2.0f; y = pac->pfTextExtent.y/2.0f; z = pac->fDepth/2.0f; r = (FLOAT) sqrt( x*x + y*y +z*z ); box.z = r; viewAngle = deg_to_rad( pac->fFovy ) / 2.0f; viewDistO = r / (FLOAT) sin( viewAngle ); viewDist = viewDistO - r; box.y = viewDist * (FLOAT) tan( viewAngle ); box.x = box.y; pac->p3dBoundingBox = box; } /**************************************************************************\ * * CalcBoundingBoxFromSpherePlus * * Same as above, but tries to optimize for case when z exent is small * \**************************************************************************/ static void CalcBoundingBoxFromSpherePlus( AttrContext *pac, FLOAT zmax ) { FLOAT x, y, z, r; FLOAT viewAngle, viewDist, viewDistO; POINT3D box; // x,y,z represent half-extents x = pac->pfTextExtent.x/2.0f; y = pac->pfTextExtent.y/2.0f; z = pac->fDepth/2.0f; r = (FLOAT) sqrt( x*x + y*y +z*z ); viewAngle = deg_to_rad( pac->fFovy ) / 2.0f; if( zmax < r ) { // we can get closer ! box.z = zmax; viewDistO = r / (FLOAT) sin( viewAngle ); viewDist = viewDistO - zmax; // we want to move the clipping plane closer by (r-zmax) if( (r-zmax) > viewDist ) { #ifdef SS_DEBUG glClearColor( 1.0f, 0.0f, 0.0f, 0.0f ); #endif // we are moving the vp inside the sphere box.y = (FLOAT) sqrt( r*r - box.z*box.z ); box.x = box.y; } else { FLOAT zt; // z-point where view frustum tangent to sphere // vp outside sphere: can only optimize if zmax < ztangent zt = r * (FLOAT) cos( PI_OVER_2 - viewAngle); if( zmax < zt ) { #ifdef SS_DEBUG // GREEN ZONE !!! glClearColor( 0.0f, 1.0f, 0.0f, 0.0f ); #endif box.y = (FLOAT) sqrt( r*r - zmax*zmax ); } else // this is the same as below, but with better clipping box.y = (viewDist + (r-zmax)) * (FLOAT) tan( viewAngle ); box.x = box.y; } } else { box.z = r; viewDistO = r / (FLOAT) sin( viewAngle ); viewDist = viewDistO - r; box.y = viewDist * (FLOAT) tan( viewAngle ); box.x = box.y; } pac->p3dBoundingBox = box; } /**************************************************************************\ * CalcBoundingBoxFromExtents * * Calculate bounding box for text, assuming text centered at origin, and * using maximum possible spin angles. * * Rotation around any one axis will affect bounding areas in the other * 2 directions (e.g. z-rotation affects x and y bounding values). * * We need to find the maxima of the rotated 2d area, while staying within * the max spin angles. * \**************************************************************************/ static void CalcBoundingBoxFromExtents( AttrContext *pac, POINT3D *box ) { POINTFLOAT extent; box->x = pac->pfTextExtent.x / 2.0f; box->y = pac->pfTextExtent.y / 2.0f; box->z = pac->fDepth / 2.0f; // split the 3d problem into 3 2d problems in 'x-y' plane if( pac->p3dRotMax.x != 0.0f ) { CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.x), box->z, box->y, &extent ); box->z = max( box->z, extent.x ); box->y = max( box->y, extent.y ); } if( pac->p3dRotMax.y != 0.0f ) { CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.y), box->x, box->z, &extent ); box->x = max( box->x, extent.x ); box->z = max( box->z, extent.y ); } if( pac->p3dRotMax.z != 0.0f ) { CalcBoundingExtent( deg_to_rad(pac->p3dRotMax.z), box->x, box->y, &extent ); box->x = max( box->x, extent.x ); box->y = max( box->y, extent.y ); } } /**************************************************************************\ * CalcBoundingBoxGeneric * * Combines the bounding sphere with the bounding extents * Each of these alone will guarantee no clipping. But we can * optimize by combining them. * \**************************************************************************/ static void CalcBoundingBoxGeneric( AttrContext *pac ) { POINT3D extentBox; FLOAT x, y, z, r, d, zt, fovx; FLOAT viewAngle, viewDist, viewDistO; BOOL xIn, yIn; // x,y,z represent half-extents x = pac->pfTextExtent.x/2.0f; y = pac->pfTextExtent.y/2.0f; z = pac->fDepth/2.0f; // get the max extent box /*!!! wait, this alone doesn't guarantee no clipping? It only * checks each axis-rotation separately, without combining them. This * is no better than calling old CalcBoundingBox ... ?? Well, I * can't prove why theoretically, but it works */ CalcBoundingBoxFromExtents( pac, &extentBox ); // determine whether x and y extents inside/outside bounding sphere r = (FLOAT) sqrt( x*x + y*y +z*z ); // check y d = (FLOAT) sqrt( extentBox.y*extentBox.y + extentBox.z*extentBox.z ); yIn = d <= r ? TRUE : FALSE; // check x d = (FLOAT) sqrt( extentBox.x*extentBox.x + extentBox.z*extentBox.z ); xIn = d <= r ? TRUE : FALSE; // handle easy cases if( yIn && xIn ) { pac->p3dBoundingBox = extentBox; return; } if( !yIn && !xIn ) { CalcBoundingBoxFromSpherePlus( pac, extentBox.z ); return; } // harder cases viewAngle = deg_to_rad( pac->fFovy ) / 2.0f; if( yIn ) { // figure out x viewDist = extentBox.y / (FLOAT) tan(viewAngle); /* viewDist can be inside or outside of the sphere * If inside - no optimization possible * If outside, can draw line from viewpoint tangent to sphere, * and use this point for x */ viewDistO = extentBox.z + viewDist; if( viewDistO <= r ) { // vp inside sphere // set x to the point where z intersects sphere // this becomes a Pythagorous theorem problem: extentBox.x = (FLOAT) sqrt( r*r - extentBox.z*extentBox.z ); } else { // vp outside sphere /* - figure out zt, where line tangent to circle for viewAngle */ fovx = (FLOAT) asin( r / viewDistO ); zt = r * (FLOAT) acos( PI_OVER_2 - viewAngle); if( extentBox.z < zt ) { // use x where extentBox.z intersects sphere extentBox.x = (FLOAT) sqrt( r*r - extentBox.z*extentBox.z ); } else { // use x at tangent point extentBox.x = (FLOAT) sqrt( r*r - zt*zt ); } } } else {// y out, x in // XXX! // have to figure out whether vp inside/outside of sphere. /* !We can cheat a bit here. It IS possible, with view angles > 90, * that the vp be inside sphere. But since we always use 90 for * this app, it is safe to assume vp > r (Fix later for general case) */ // XXX: wait, if y out, isn't vp always outside sphere ? /* So we solve it this way: * - figure out line tangent to circle for viewAngle * - y will be where this line intersects the z=extentBox.z line */ viewDistO = r / (FLOAT) sin( viewAngle ); extentBox.y = (viewDistO - extentBox.z) * (FLOAT) tan( viewAngle ); // I guess don't have to do anything with x ? } pac->p3dBoundingBox = extentBox; } /**************************************************************************\ * * Calculate the extents in x and y from rotating a rectangle in a 2d plane * \**************************************************************************/ static void CalcBoundingExtent( FLOAT rot, FLOAT x, FLOAT y, POINTFLOAT *extent ) { FLOAT r, angleCrit; r = (FLOAT) sqrt( x*x + y*y ); angleCrit = (x == 0.0f) ? PI_OVER_2 : (FLOAT) atan( y/x ); // calc incursion in x if( rot >= angleCrit ) { // easy, use maximum possible extent extent->x = r; } else { // rotate lower right corner of box by rot to get extent extent->x = x * (FLOAT) cos( rot ) + y * (FLOAT) sin( rot ); } // calc incursion in y angleCrit = PI/2.0f - angleCrit; if( rot >= angleCrit ) { // easy, use maximum possible extent extent->y = r; } else { // rotate upper right corner of box by rot to get extent extent->y = x * (FLOAT) sin( rot ) + y * (FLOAT) cos( rot ); } } /**************************************************************************\ * * Sorts in descending order, based on values in val array (bubble sort) * \**************************************************************************/ static void SortBoxes( POINT3D *box, FLOAT *val, int numBox ) { int i, j, t; POINT3D temp; j = numBox; while( j ) { t = 0; for( i = 0; i < j-1; i++ ) { if( val[i] < val[i+1] ) { // swap'em temp = box[i]; box[i] = box[i+1]; box[i+1] = temp; t = i; } } j = t; } } #define FILE_BUF_SIZE 180 /**************************************************************************\ * VerifyString * * Validate the string * * Has hard-coded ascii routines * \**************************************************************************/ static BOOL VerifyString( AttrContext *pac ) { HMODULE ghmodule; HRSRC hr; HGLOBAL hg; PSZ psz, pszFile = NULL; CHAR szSectName[30], szFileName[FILE_BUF_SIZE], szFname[30]; BOOL bMatch = FALSE; // Check for string file in registry if (LoadStringA(hMainInstance, IDS_SAVERNAME, szSectName, 30) && LoadStringA(hMainInstance, IDS_INIFILE, szFname, 30)) { if( GetPrivateProfileStringA(szSectName, "magic", NULL, szFileName, FILE_BUF_SIZE, szFname) ) pszFile = ReadStringFileA( szFileName ); } // Check for key strings if( pszFile ) bMatch = CheckKeyStrings( pac->szText, pszFile ); if( !bMatch ) { if( (ghmodule = GetModuleHandle(NULL)) && (hr = FindResource(ghmodule, MAKEINTRESOURCE(1), MAKEINTRESOURCE(99))) && (hg = LoadResource(ghmodule, hr)) && (psz = (PSZ)LockResource(hg)) ) bMatch = CheckKeyStrings( pac->szText, psz ); } if( bMatch ) { // put first string in pac->szText pac->demoType = DEMO_VSTRING; // for now, initialize strings here text3d_UpdateString( pac, FALSE ); // adjust cycle time based on rotStyle switch( pac->rotStyle ) { case ROTSTYLE_NONE: gfMinCycleTime = 4.0f; break; case ROTSTYLE_SEESAW: gfMinCycleTime = 8.0f; break; case ROTSTYLE_RANDOM: gfMinCycleTime = 10.0f; break; default: gfMinCycleTime = 9.0f; } } if( pszFile ) free( pszFile ); // allocated by ReadStringFile return bMatch; } /**************************************************************************\ * CheckKeyStrings * * Test for match between string and any keystrings * 'string' is user-inputted, and limited to TEXT_LIMIT chars. * \**************************************************************************/ static BOOL CheckKeyStrings( LPTSTR string, PSZ psz ) { int i; TCHAR szKey[TEXT_LIMIT+1], testString[TEXT_LIMIT+1] = {0}; BOOL bMatch = FALSE; int nMatch = 0; int len; // make copy of test string and convert to upper case lstrcpy( testString, string ); #ifdef UNICODE _wcsupr( testString ); #else _strupr( testString ); #endif while( psz[0] != '\n' ) { // iterate keyword/data sets while( psz[0] != '\n' ) { // iterate keywords len = strlen( psz ); // ! could be > TEXT_LIMIT if from file // invert keyword bits and convert to uppercase #ifdef UNICODE // convert ascii keyword to unicode in szKey (inverts at same time) ConvertStringAsciiToUnicode( psz, szKey, len > TEXT_LIMIT ? TEXT_LIMIT : len ); _wcsupr( szKey ); #else // just copy keyword to szKey, without going over TEXT_LIMIT strncpy( szKey, psz, TEXT_LIMIT ); InvertBitsA( szKey, len > TEXT_LIMIT ? TEXT_LIMIT : len ); szKey[TEXT_LIMIT] = '\0'; // in case len > TEXT_LIMIT _strupr( szKey ); #endif if( !lstrcmp( szKey, testString ) ) { // keyword match ! bMatch = TRUE; nMatch++; } psz += len + 1; // skip over NULL as well } psz++; // skip over '\n' at end of keywords if( bMatch ) ReadNameList( psz ); // skip over data to get to next keyword group while( *psz != '\n' ) psz++; psz++; // skip over '\n' at end of data bMatch = FALSE; // keep searching for keyword matches } return nMatch; } /**************************************************************************\ * Various functions to process vstrings * \**************************************************************************/ static void InvertBitsA( char *s, int len ) { while( len-- ) { *s++ = ~(*s); } } static PSZ ReadStringFileA( LPSTR szFile ) { char lineBuf[180]; PSZ buf, pBuf; int size, length, fdi; char *ps; char ctrl_n = '\n'; FILE *fIn; BOOL bKey; // create buffer to hold entire file // mf: ! must be better way of getting file length! fdi = _open(szFile, O_RDONLY | O_BINARY); if( fdi < 0 ) return NULL; size = _filelength( fdi ); _close(fdi); buf= (char *) malloc( size ); if( !buf) return NULL; // open file for ascii text reading fIn = fopen( szFile, "r" ); if( !fIn ) return NULL; // Read in keyword/data sequences bKey = TRUE; // so '\n' not appended to file when hit first keyword pBuf = buf; while( fgets( lineBuf, 180, fIn) ) { ps = lineBuf; if( *ps == '-' ) { // keyword if( !bKey ) { // first key in group, append '\n' to data *pBuf++ = ctrl_n; } bKey = TRUE; ps++; // skip '-' } else { // data if( bKey ) { // first data in group, append '\n' to keywords *pBuf++ = ctrl_n; } bKey = FALSE; } length = strlen( ps ); InvertBitsA( ps, length ); *(ps+length-1) = '\0'; // convert '\n' to null lstrcpyA( pBuf, ps ); pBuf += length; } fclose( fIn ); // put 2 '\n' at end, for end condition *pBuf++ = ctrl_n; *pBuf++ = ctrl_n; return( buf ); } static void CreateRandomList() { PLIST plist = gplistComplete; PLIST *pplist; int i = 0; int n; while (plist != NULL) { n = ss_iRand( i+1 ); pplist = &gplist; while (n > 0) { pplist = &((*pplist)->pnext); n--; } plist->pnext = *pplist; *pplist = plist; plist = plist->plistComplete; i++; } } static void AddName( LPTSTR pszStr) { PLIST plist = (PLIST)LocalAlloc(LPTR, sizeof(LIST)); if( !plist ) return; plist->pszStr = pszStr; plist->pnext = NULL; plist->plistComplete = gplistComplete; gplistComplete = plist; } static void ReadNameList( PSZ psz ) { int length; int i; LPTSTR pszNew; while (psz[0] != '\n') { length = 0; while (psz[length] != 0) { length++; } length; pszNew = (LPTSTR)LocalAlloc( LPTR, (length + 1)*sizeof(TCHAR) ); if( !pszNew ) return; #ifdef UNICODE ConvertStringAsciiToUnicode( psz, (PWSTR) pszNew, length ); #else strncpy( pszNew, psz, length ); InvertBitsA( pszNew, length ); #endif AddName(pszNew); psz += length + 1; } } static void DeleteNameList() { PLIST plist = gplistComplete, plistLast; while( plist != NULL ) { LocalFree( plist->pszStr ); plistLast = plist; plist = plist->plistComplete; LocalFree( plistLast ); } } /**************************************************************************\ * ConvertStringAsciiToUnicorn * \**************************************************************************/ static void ConvertStringAsciiToUnicode( PSZ psz, PWSTR pwstr, int len ) { while( len-- ) *pwstr++ = ~(*psz++) & 0xFF; *pwstr = 0; // null terminate } /**************************************************************************\ * FrameCalibration * * Adjusts the number of frames in a cycle to conform to desired cycle time * \**************************************************************************/ static int FrameCalibration( AttrContext *pac, struct _timeb *pBaseTime, int framesPerCycle, int nCycle ) { struct _timeb thisTime; FLOAT cycleTime; _ftime( &thisTime ); cycleTime = thisTime.time - pBaseTime->time + (thisTime.millitm - pBaseTime->millitm)/1000.0f; cycleTime /= (FLOAT) nCycle; if( cycleTime < gfMinCycleTime ) { // need to add more frames to cycle if( cycleTime == 0.0f ) // very unlikely framesPerCycle = 800; else framesPerCycle = (int)( (FLOAT)framesPerCycle * (gfMinCycleTime/cycleTime) ); } else { // for vstrings, subtract frames from cycle if( pac->demoType == DEMO_VSTRING ) { framesPerCycle = (int)( (FLOAT)framesPerCycle * (gfMinCycleTime/cycleTime) ); } } #define MIN_FRAMES 16 // make sure it's not too small if( framesPerCycle < MIN_FRAMES ) framesPerCycle = MIN_FRAMES; return framesPerCycle; } /**************************************************************************\ * MapValue * * Maps the value along an input range, to a proportional one along an * output range. Each range must be monotonically increasing or decreasing. * * NO boundary conditions checked - responsibility of caller. * \**************************************************************************/ FLOAT MapValue( FLOAT fInVal, FLOAT fIn1, FLOAT fIn2, // input range FLOAT fOut1, FLOAT fOut2 ) // output range { FLOAT fDist, fOutVal; // how far along the input range is fInVal?, in % fDist = (fInVal - fIn1) / (fIn2 - fIn1); // use this distance to interpolate into output range fOutVal = fDist * (fOut2 - fOut1) + fOut1; return fOutVal; } /**************************************************************************\ * MapValueI * * Similar to above, but maps integer values * * Currently, only works for increasing ranges * * History * Apr. 28, 95 : [marcfo] * - Added early return for boundary conditions * \**************************************************************************/ int MapValueI( int inVal, int in1, int in2, // input range int out1, int out2 ) // output range { int inDiv; int outVal; FLOAT fScale, fComp; if( inVal >= in2 ) return out2; if( inVal <= in1 ) return out1; inDiv = abs(in2 - in1) + 1; fScale = (FLOAT) (inDiv-1) / (FLOAT) inDiv; fComp = 1.0f + (1.0f / inDiv); outVal = (int) MapValue( (FLOAT) inVal * fComp, (FLOAT) in1, (FLOAT) in2 + 0.999f, (FLOAT) out1, (FLOAT) out2 + 0.999f ); return outVal; }