2399 lines
72 KiB
C
2399 lines
72 KiB
C
/******************************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 <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#include <sys/types.h>
|
|
#include <sys/timeb.h>
|
|
#include <time.h>
|
|
#include <windows.h>
|
|
#include <scrnsave.h>
|
|
#include <GL/gl.h>
|
|
#include <GL/glu.h>
|
|
#include <GL/glaux.h>
|
|
|
|
//#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;
|
|
}
|