home *** CD-ROM | disk | FTP | other *** search
/ Apple WWDC 1996 / WWDC96_1996 (CD).toast / Technology Materials / QuickTime VR / Windows / Samples / QD3DSample / Box3DSupport.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-01  |  16.2 KB  |  587 lines  |  [TEXT/LMAN]

  1. // Box3dSupport.c - QuickDraw 3d routines
  2. //
  3. // This file contains utility routines for QuickDraw 3d sample code.
  4. // This is a simple QuickDraw 3d application to draw a cube in the center 
  5. // of the main application window.  The routines in here handle setting up
  6. // the main display group, the view, the Macintosh 3D draw context, and the
  7. // camera and lighting. 
  8. //
  9. // This code is the basis of the introductory article in  d e v e l o p  issue 22
  10. //
  11. // Nick Thompson - January 6th 1995
  12. // ©1994-95 Apple computer Inc., All Rights Reserved
  13. // 
  14. //
  15. #include "Box3DSupport.h"
  16.  
  17. #define ErMath_Atan(x)    ((float)atan((double)(x)))
  18. #define    kEPSILON        1.19209290e-07
  19.  
  20.  
  21. static     TQ3Point3D    documentGroupCenter;
  22. static    float        documentGroupScale;
  23.  
  24.  
  25. TQ3ViewObject MyNewView(DocumentPtr theDocument)
  26. {
  27.     TQ3Status                myStatus;
  28.     TQ3ViewObject            myView;
  29.     TQ3DrawContextObject    myDrawContext;
  30.     TQ3RendererObject        myRenderer;
  31.     TQ3CameraObject            myCamera;
  32.     TQ3GroupObject            myLights;
  33.     
  34.     myView = Q3View_New();
  35.     
  36.     //    Create and set draw context.
  37.     if ((myDrawContext = MyNewDrawContext(theDocument)) == NULL )
  38.         goto bail;
  39.         
  40.     if ((myStatus = Q3View_SetDrawContext(myView, myDrawContext)) == kQ3Failure )
  41.         goto bail;
  42.  
  43.     Q3Object_Dispose( myDrawContext ) ;
  44.     
  45.     //    Create and set renderer.
  46.     
  47.     // this uses the wire frame renderer
  48. #if 0
  49.  
  50.     myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeWireFrame);
  51.     if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  52.         goto bail;
  53.     }
  54.     
  55. #else
  56.  
  57.     // this uses the interactive software renderer
  58.  
  59.     if ((myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeInteractive)) != NULL ) {
  60.         if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  61.             goto bail;
  62.         }
  63.         // these two lines set us up to use the best possible renderer,
  64.         // including  hardware if it is installed.
  65.         Q3InteractiveRenderer_SetDoubleBufferBypass (myRenderer, kQ3True);                        
  66.         Q3InteractiveRenderer_SetPreferences(myRenderer, kQAVendor_BestChoice, 0);
  67.  
  68.     }
  69.     else {
  70.         goto bail;
  71.     }
  72. #endif
  73.  
  74.     Q3Object_Dispose( myRenderer ) ;
  75.     
  76.     //    Create and set camera
  77.     if ( (myCamera = MyNewCamera(theDocument)) == NULL )
  78.         goto bail;
  79.         
  80.     if ((myStatus = Q3View_SetCamera(myView, myCamera)) == kQ3Failure )
  81.         goto bail;
  82.  
  83.     Q3Object_Dispose( myCamera ) ;
  84.     
  85.     //    Create and set lights
  86.     if ((myLights = MyNewLights()) == NULL )
  87.         goto bail;
  88.         
  89.     if ((myStatus = Q3View_SetLightGroup(myView, myLights)) == kQ3Failure )
  90.         goto bail;
  91.         
  92.     Q3Object_Dispose(myLights);
  93.  
  94.     return ( myView );
  95.     
  96. bail:
  97.     //    If any of the above failed, then don't return a view.
  98.     return ( NULL );
  99. }
  100.  
  101. //----------------------------------------------------------------------------------
  102.  
  103. TQ3DrawContextObject MyNewDrawContext(DocumentPtr theDocument)
  104. {
  105.     TQ3DrawContextData            myDrawContextData;
  106.     TQ3PixmapDrawContextData    myPixmapDrawContextData;
  107.     TQ3DrawContextObject        myDrawContext ;
  108.     TQ3Pixmap                    aPixmap;
  109.      BITMAPINFO                    bitmapInfo;
  110.     HDC                            hdc;
  111.  
  112.         //    fill in draw context data.
  113.     myDrawContextData.clearImageMethod = kQ3ClearMethodWithColor;
  114.         //    Set the background color.
  115.     myDrawContextData.clearImageColor.a = 1.0F;
  116.     myDrawContextData.clearImageColor.r = 1.0F;
  117.     myDrawContextData.clearImageColor.g = 1.0F;
  118.     myDrawContextData.clearImageColor.b = 1.0F;
  119.  
  120.     myDrawContextData.paneState = kQ3False;
  121.     myDrawContextData.maskState = kQ3False;
  122.     myDrawContextData.doubleBufferState = kQ3False;
  123.  
  124.         // set up device independent bitmap
  125.     hdc = GetDC(theDocument->fWindow);
  126.     theDocument->fMemoryDC = CreateCompatibleDC(hdc);
  127.  
  128.     bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);    
  129.     bitmapInfo.bmiHeader.biWidth = theDocument->fWidth;    
  130.     bitmapInfo.bmiHeader.biHeight = -theDocument->fHeight;     // negative means top to bottom
  131.     bitmapInfo.bmiHeader.biPlanes = 1;
  132.     if ( GetDeviceCaps(hdc, BITSPIXEL) == 32 )
  133.         bitmapInfo.bmiHeader.biBitCount = 32;
  134.     else
  135.         bitmapInfo.bmiHeader.biBitCount = 16;    
  136.     bitmapInfo.bmiHeader.biCompression = BI_RGB;    
  137.     bitmapInfo.bmiHeader.biSizeImage = 0;    
  138.     bitmapInfo.bmiHeader.biXPelsPerMeter = 0;    
  139.     bitmapInfo.bmiHeader.biYPelsPerMeter = 0;    
  140.     bitmapInfo.bmiHeader.biClrUsed = 0;    
  141.     bitmapInfo.bmiHeader.biClrImportant = 0;    
  142.  
  143.     theDocument->fBitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS,    
  144.                                             &theDocument->fBitStorage, NULL, 0);    
  145.     SelectObject(theDocument->fMemoryDC, theDocument->fBitmap);
  146.  
  147.         /* create a pixmap */
  148.     aPixmap.width = theDocument->fWidth;
  149.     aPixmap.height = theDocument->fHeight;
  150.     aPixmap.image = theDocument->fBitStorage;
  151.     if (bitmapInfo.bmiHeader.biBitCount == 32)
  152.     {
  153.         aPixmap.rowBytes = aPixmap.width * 4;
  154.         aPixmap.pixelSize = 32;
  155.         aPixmap.pixelType = kQ3PixelTypeRGB32;
  156.     }
  157.     else
  158.     {
  159.         aPixmap.rowBytes = (aPixmap.width * (bitmapInfo.bmiHeader.biBitCount/8));
  160.         aPixmap.rowBytes = (aPixmap.rowBytes + 3) & ~3L; // make it long aligned
  161.         aPixmap.pixelSize = 16;
  162.         aPixmap.pixelType = kQ3PixelTypeRGB16;
  163.     }
  164.     aPixmap.bitOrder = kQ3EndianLittle;   
  165.     aPixmap.byteOrder = kQ3EndianLittle; 
  166.  
  167.         /* set up the pixmapDrawContext */
  168.     myPixmapDrawContextData.pixmap = aPixmap;
  169.     myPixmapDrawContextData.drawContextData = myDrawContextData;
  170.  
  171.     //    Create draw context and return it, if it's nil the caller must handle
  172.     myDrawContext = Q3PixmapDrawContext_New(&myPixmapDrawContextData) ;
  173.  
  174.     return myDrawContext ;
  175. }
  176.  
  177. //----------------------------------------------------------------------------------
  178.  
  179. TQ3CameraObject MyNewCamera(DocumentPtr theDocument)
  180. {
  181.     TQ3ViewAngleAspectCameraData    perspectiveData;
  182.  
  183.     TQ3CameraObject                camera;
  184.     
  185.     TQ3Point3D                     from     = { 0.0F, 0.0F, 7.0F };
  186.     TQ3Point3D                     to         = { 0.0F, 0.0F, 0.0F };
  187.     TQ3Vector3D                 up         = { 0.0F, 1.0F, 0.0F };
  188.  
  189.     float                         fieldOfView = 1.0F;
  190.     float                         hither         = 0.001F;
  191.     float                         yon         = 1000.0F;
  192.  
  193.     TQ3Status                    returnVal = kQ3Failure ;
  194.  
  195.     perspectiveData.cameraData.placement.cameraLocation     = from;
  196.     perspectiveData.cameraData.placement.pointOfInterest     = to;
  197.     perspectiveData.cameraData.placement.upVector             = up;
  198.  
  199.     perspectiveData.cameraData.range.hither    = hither;
  200.     perspectiveData.cameraData.range.yon     = yon;
  201.  
  202.     perspectiveData.cameraData.viewPort.origin.x = -1.0F;
  203.     perspectiveData.cameraData.viewPort.origin.y = 1.0F;
  204.     perspectiveData.cameraData.viewPort.width = 2.0F;
  205.     perspectiveData.cameraData.viewPort.height = 2.0F;
  206.                                                     
  207.     perspectiveData.fov                = fieldOfView;
  208.     perspectiveData.aspectRatioXToY    =
  209.         (float) (theDocument->fWidth) / 
  210.         (float) (theDocument->fHeight);
  211.         
  212.     camera = Q3ViewAngleAspectCamera_New(&perspectiveData);
  213.  
  214.     return camera ;
  215. }
  216.  
  217.  
  218. //----------------------------------------------------------------------------------
  219.  
  220. TQ3GroupObject MyNewLights()
  221. {
  222.     TQ3GroupPosition        myGroupPosition;
  223.     TQ3GroupObject            myLightList;
  224.     TQ3LightData            myLightData;
  225.     TQ3PointLightData        myPointLightData;
  226.     TQ3DirectionalLightData    myDirectionalLightData;
  227.     TQ3LightObject            myAmbientLight, myPointLight, myFillLight;
  228.     TQ3Point3D                pointLocation = { -10.0F, 0.0F, 10.0F };
  229.     TQ3Vector3D                fillDirection = { 10.0F, 0.0F, 10.0F };
  230.     TQ3ColorRGB                WhiteLight = { 1.0F, 1.0F, 1.0F };
  231.     
  232.     //    Set up light data for ambient light.  This light data will be used for point and fill
  233.     //    light also.
  234.  
  235.     myLightData.isOn = kQ3True;
  236.     myLightData.color = WhiteLight;
  237.     
  238.     //    Create ambient light.
  239.     myLightData.brightness = .25F;
  240.     myAmbientLight = Q3AmbientLight_New(&myLightData);
  241.     if ( myAmbientLight == NULL )
  242.         goto bail;
  243.     
  244.     //    Create point light.
  245.     myLightData.brightness = 1.0F;
  246.     myPointLightData.lightData = myLightData;
  247.     myPointLightData.castsShadows = kQ3False;
  248.     myPointLightData.attenuation = kQ3AttenuationTypeNone;
  249.     myPointLightData.location = pointLocation;
  250.     myPointLight = Q3PointLight_New(&myPointLightData);
  251.     if ( myPointLight == NULL )
  252.         goto bail;
  253.  
  254.     //    Create fill light.
  255.     myLightData.brightness = .3F;
  256.     myDirectionalLightData.lightData = myLightData;
  257.     myDirectionalLightData.castsShadows = kQ3False;
  258.     myDirectionalLightData.direction = fillDirection;
  259.     myFillLight = Q3DirectionalLight_New(&myDirectionalLightData);
  260.     if ( myFillLight == NULL )
  261.         goto bail;
  262.  
  263.     //    Create light group and add each of the lights into the group.
  264.     myLightList = Q3LightGroup_New();
  265.     if ( myLightList == NULL )
  266.         goto bail;
  267.     myGroupPosition = Q3Group_AddObject(myLightList, myAmbientLight);
  268.     if ( myGroupPosition == 0 )
  269.         goto bail;
  270.     myGroupPosition = Q3Group_AddObject(myLightList, myPointLight);
  271.     if ( myGroupPosition == 0 )
  272.         goto bail;
  273.     myGroupPosition = Q3Group_AddObject(myLightList, myFillLight);
  274.     if ( myGroupPosition == 0 )
  275.         goto bail;
  276.  
  277.     Q3Object_Dispose( myAmbientLight ) ;
  278.     Q3Object_Dispose( myPointLight ) ;
  279.     Q3Object_Dispose( myFillLight ) ;
  280.  
  281.     //    Done!
  282.     return ( myLightList );
  283.     
  284. bail:
  285.     //    If any of the above failed, then return nothing!
  286.     return ( NULL );
  287. }
  288.  
  289.  
  290.  
  291.  
  292. static void MyColorBoxFaces( TQ3BoxData *myBoxData )
  293. {
  294.     TQ3ColorRGB                faceColor ;
  295.     short                     face ;
  296.     
  297.     // sanity check - you need to have set up 
  298.     // the face attribute set for the box data 
  299.     // before calling this.
  300.     
  301.     if( myBoxData->faceAttributeSet == NULL )
  302.         return ;
  303.         
  304.     // make each face of a box a different color
  305.     
  306.     for( face = 0; face < 6; face++) {
  307.         
  308.         myBoxData->faceAttributeSet[face] = Q3AttributeSet_New();
  309.         switch( face ) {
  310.             case 0:
  311.                 faceColor.r = 1.0F;
  312.                 faceColor.g = 0.0F;
  313.                 faceColor.b = 0.0F;
  314.                 break;
  315.             
  316.             case 1:
  317.                 faceColor.r = 0.0F;
  318.                 faceColor.g = 1.0F;
  319.                 faceColor.b = 0.0F;
  320.                 break;
  321.             
  322.             case 2:
  323.                 faceColor.r = 0.0F;
  324.                 faceColor.g = 0.0F;
  325.                 faceColor.b = 1.0F;
  326.                 break;
  327.             
  328.             case 3:
  329.                 faceColor.r = 1.0F;
  330.                 faceColor.g = 1.0F;
  331.                 faceColor.b = 0.0F;
  332.                 break;
  333.             
  334.             case 4:
  335.                 faceColor.r = 1.0F;
  336.                 faceColor.g = 0.0F;
  337.                 faceColor.b = 1.0F;
  338.                 break;
  339.             
  340.             case 5:
  341.                 faceColor.r = 0.0F;
  342.                 faceColor.g = 1.0F;
  343.                 faceColor.b = 1.0F;
  344.                 break;
  345.         }
  346.         Q3AttributeSet_Add(myBoxData->faceAttributeSet[face], kQ3AttributeTypeDiffuseColor, &faceColor);
  347.     }
  348. }
  349.  
  350. static TQ3GroupPosition MyAddTransformedObjectToGroup( TQ3GroupObject theGroup, TQ3Object theObject, TQ3Vector3D *translation )
  351. {
  352.     TQ3TransformObject    transform;
  353.  
  354.     transform = Q3TranslateTransform_New(translation);
  355.     Q3Group_AddObject(theGroup, transform);    
  356.     Q3Object_Dispose(transform);
  357.     return Q3Group_AddObject(theGroup, theObject);    
  358. }
  359.  
  360.  
  361. TQ3GroupObject MyNewModel()
  362. {
  363.     TQ3GroupObject            myGroup = NULL;
  364.     TQ3GeometryObject        myBox;
  365.     TQ3BoxData                myBoxData;
  366.     TQ3ShaderObject            myIlluminationShader ;
  367.     
  368.     TQ3SetObject            faces[6] ;
  369.     short                    face ;
  370.             
  371.     // Create a group for the complete model.
  372.     // do not use Q3OrderedDisplayGroup_New since in this
  373.     // type of group all of the translations are applied before
  374.     // the objects in the group are drawn, in this instance we 
  375.     // dont want this.
  376.     if ((myGroup = Q3DisplayGroup_New()) != NULL ) {
  377.             
  378.         // Define a shading type for the group
  379.         // and add the shader to the group
  380.         myIlluminationShader = Q3PhongIllumination_New();
  381.         Q3Group_AddObject(myGroup, myIlluminationShader);
  382.  
  383.         // set up the colored faces for the box data
  384.         myBoxData.faceAttributeSet = faces;
  385.         myBoxData.boxAttributeSet = NULL;
  386.         MyColorBoxFaces( &myBoxData ) ;
  387.         
  388.         #define    kBoxSide        0.8F
  389.         #define    kBoxSidePlusGap    0.1F
  390.  
  391.         // create the box itself
  392.         Q3Point3D_Set(&myBoxData.origin, 0.0F, 0.0F, 0.0F);
  393.         Q3Vector3D_Set(&myBoxData.orientation, 0.0F, kBoxSide, 0.0F);
  394.         Q3Vector3D_Set(&myBoxData.majorAxis, 0.0F, 0.0F, kBoxSide);    
  395.         Q3Vector3D_Set(&myBoxData.minorAxis, kBoxSide, 0.0F, 0.0F);    
  396.         myBox = Q3Box_New(&myBoxData);
  397. #if 0    // one box
  398.         Q3Group_AddObject(myGroup, myBox);
  399. #else    // 4 boxes
  400.         {
  401.             TQ3Vector3D                translation;
  402.             translation.x =  0.0F;                
  403.             translation.y = kBoxSidePlusGap; 
  404.             translation.z =  0.0F;
  405.             MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  406.             translation.x =  2 * kBoxSide;    
  407.             translation.y = kBoxSidePlusGap; 
  408.             translation.z =  0.0F;
  409.             MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  410.             translation.x =  0.0F;                
  411.             translation.y = kBoxSidePlusGap; 
  412.             translation.z = -2 * kBoxSide;
  413.             MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  414.             translation.x = -2 * kBoxSide;    
  415.             translation.y = kBoxSidePlusGap; 
  416.             translation.z =  0.0F;
  417.             MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  418.         }
  419. #endif
  420.     }
  421.     
  422.     // dispose of the objects we created here
  423.     if( myIlluminationShader ) 
  424.         Q3Object_Dispose(myIlluminationShader);    
  425.             
  426.     for( face = 0; face < 6; face++) {
  427.         if( myBoxData.faceAttributeSet[face] != NULL )
  428.             Q3Object_Dispose(myBoxData.faceAttributeSet[face]);
  429.     }
  430.     
  431.     if( myBox ) 
  432.         Q3Object_Dispose( myBox );
  433.     
  434.     //    Done!
  435.     return ( myGroup );
  436. }
  437.  
  438. void pvCamera_Fit(DocumentPtr theDocument)
  439. {    
  440.     TQ3Point3D         from, to;
  441.     TQ3BoundingBox    viewBBox;
  442.     float            fieldOfView, hither, yon;
  443.  
  444.     if (!theDocument)
  445.         return;
  446.  
  447.     if (!theDocument->fModel)
  448.         return;
  449.  
  450.     pvBBox_Get(theDocument, &viewBBox);
  451.     pvBBoxCenter(&viewBBox, &to);
  452.     
  453.  
  454.     {
  455.         TQ3Vector3D viewVector;
  456.         TQ3Vector3D    normViewVector;
  457.         TQ3Vector3D    eyeToFrontClip;
  458.         TQ3Vector3D    eyeToBackClip;
  459.          TQ3Vector3D    diagonalVector;
  460.         float        viewDistance;
  461.         float         maxDimension;
  462.  
  463.         Q3Point3D_Subtract(&viewBBox.max,
  464.                            &viewBBox.min,
  465.                            &diagonalVector);
  466.         maxDimension = Q3Vector3D_Length(&diagonalVector);
  467.         if (maxDimension == 0.0F)
  468.             maxDimension = 1.0F;
  469.  
  470.         maxDimension *= 8.0F / 7.0F;
  471.     
  472.         from.x = to.x;
  473.         from.y = to.y;
  474.         from.z = to.z + (2 * maxDimension);        
  475.  
  476.         Q3Point3D_Subtract(&to, &from, &viewVector);
  477.         viewDistance = Q3Vector3D_Length(&viewVector);
  478.         Q3Vector3D_Normalize(&viewVector, &normViewVector);
  479.         
  480.         maxDimension /= 2.0F;
  481.         
  482.         Q3Vector3D_Scale(&normViewVector, 
  483.                         viewDistance - maxDimension,
  484.                         &eyeToFrontClip);
  485.                         
  486.         Q3Vector3D_Scale(&normViewVector, 
  487.                         viewDistance + maxDimension,
  488.                         &eyeToBackClip);
  489.         
  490.         hither     = Q3Vector3D_Length(&eyeToFrontClip);
  491.         yon     = Q3Vector3D_Length(&eyeToBackClip);
  492.         
  493.         fieldOfView = Q3Math_RadiansToDegrees(1.25 * ErMath_Atan(maxDimension/hither));
  494.     }
  495.  
  496.     {
  497.         TQ3ViewAngleAspectCameraData     data;
  498.         TQ3Vector3D     up     = { 0.0F, 1.0F, 0.0F };
  499.  
  500.         data.cameraData.placement.cameraLocation     = from;
  501.         data.cameraData.placement.pointOfInterest     = to;
  502.         data.cameraData.placement.upVector             = up;
  503.     
  504.         data.cameraData.range.hither = hither;
  505.         data.cameraData.range.yon      = yon;
  506.     
  507.         data.cameraData.viewPort.origin.x = -1.0F;
  508.         data.cameraData.viewPort.origin.y =  1.0F;
  509.         data.cameraData.viewPort.width  = 2.0F;
  510.         data.cameraData.viewPort.height = 2.0F;
  511.  
  512.         data.fov = Q3Math_DegreesToRadians(fieldOfView);
  513.         
  514.         {
  515.             float w = (float)(theDocument->fWidth);
  516.             float h = (float)(theDocument->fHeight);
  517.  
  518.             data.aspectRatioXToY = w/h;
  519.         }
  520.         
  521.         if (theDocument->fView)
  522.         {
  523.             TQ3CameraObject camera;
  524.             
  525.             Q3View_GetCamera(theDocument->fView, &camera);
  526.             if (camera) {
  527.                 Q3ViewAngleAspectCamera_SetData(camera, &data);
  528.                 Q3Object_Dispose(camera);
  529.             }
  530.             else {
  531.                 camera  = Q3ViewAngleAspectCamera_New (&data);
  532.                 if (camera) {
  533.                     Q3View_SetCamera (theDocument->fView, camera);
  534.                     Q3Object_Dispose(camera);
  535.                 }
  536.             }
  537.         }
  538.     }
  539. }
  540.  
  541. void pvBBox_Get(DocumentPtr theDocument, TQ3BoundingBox *bbox)
  542. {            
  543.     if (theDocument->fView)
  544.     {
  545.         Q3View_StartBoundingBox(theDocument->fView, kQ3ComputeBoundsExact);
  546.         do
  547.         {
  548.             if (Q3DisplayGroup_Submit(theDocument->fModel, theDocument->fView) == kQ3Failure)
  549.             {
  550.                 Q3View_Cancel(theDocument->fView);
  551.                 return;
  552.             }
  553.         } while (Q3View_EndBoundingBox(theDocument->fView, bbox) == kQ3ViewStatusRetraverse);
  554.     }
  555.     else
  556.     {
  557.         Q3Point3D_Set(&(bbox->min), -0.1F, -0.1F, -0.1F);
  558.         Q3Point3D_Set(&(bbox->max), 0.1F, 0.1F, 0.1F);
  559.         bbox->isEmpty = kQ3False;
  560.     }
  561. }
  562.  
  563. void pvBBoxCenter(TQ3BoundingBox *bbox, TQ3Point3D *center)
  564. {
  565.      float    xSize, ySize, zSize;
  566.  
  567.     xSize = bbox->max.x - bbox->min.x;
  568.     ySize = bbox->max.y - bbox->min.y;
  569.     zSize = bbox->max.z - bbox->min.z;
  570.  
  571.     if (xSize <= kEPSILON &&
  572.         ySize <= kEPSILON &&
  573.         zSize <= kEPSILON)  {
  574.         bbox->max.x += 0.0001F;
  575.         bbox->max.y += 0.0001F;
  576.         bbox->max.z += 0.0001F;
  577.         
  578.         bbox->min.x -= 0.0001F;
  579.         bbox->min.y -= 0.0001F;
  580.         bbox->min.z -= 0.0001F;
  581.     }
  582.     
  583.     center->x = (bbox->min.x + bbox->max.x) / 2.0F;
  584.     center->y = (bbox->min.y + bbox->max.y) / 2.0F;
  585.     center->z = (bbox->min.z + bbox->max.z) / 2.0F;
  586. }
  587.