home *** CD-ROM | disk | FTP | other *** search
/ Apple WWDC 1996 / WWDC96_1996 (CD).toast / Technology Materials / QuickTime VR / MacOS / QuickDraw™ 3D 1.0.6F4 SDK / Samples / SampleCode / MetafileRead / MetaFileReadSupport.c < prev    next >
Encoding:
Text File  |  1995-11-13  |  17.6 KB  |  656 lines  |  [TEXT/MPCC]

  1. // QuickDraw 3d Sample Code
  2. //
  3. // This file contains utility routines for QuickDraw 3d sample code.
  4. // Shows how to read a metafile and render it.
  5. //
  6. // Created 27th Dec 1994, Nick Thompson, DEVSUPPORT
  7.  
  8.  
  9. #include <Files.h>
  10. #include <QuickDraw.h>
  11. #include <QDOffScreen.h>
  12. #include <StandardFile.h>
  13.  
  14. #include "MetaFileReadSupport.h"
  15.  
  16. #include "QD3D.h"
  17. #include "QD3DDrawContext.h"
  18. #include "QD3DRenderer.h"
  19. #include "QD3DShader.h"
  20. #include "QD3DCamera.h"
  21. #include "QD3DLight.h"
  22. #include "QD3DGeometry.h"
  23. #include "QD3DTransform.h"
  24. #include "QD3DGroup.h"
  25. #include "QD3DMath.h"
  26.  
  27. #include "QD3DStorage.h"
  28. #include "QD3DIO.h"
  29.  
  30.  
  31. //-----------------------------------------------------------------------------------------------
  32. // local utility functions
  33. static    TQ3FileObject         MyGetNewFile( FSSpec *myFSSpec, TQ3Boolean *isText ) ;
  34.  
  35. void GetGroupBBox(
  36.     DocumentPtr            theDocument,
  37.     TQ3BoundingBox         *viewBBox) ;
  38.                                                 
  39. static    TQ3Status MyAddShaderToGroup( TQ3GroupObject group ) ;
  40.  
  41. static TQ3Status GetDocumentGroupBoundingBox( 
  42.     DocumentPtr theDocument , 
  43.     TQ3BoundingBox *viewBBox) ;
  44.  
  45. //-----------------------------------------------------------------------------------------------
  46. // Submit the scene for rendering/fileIO and picking
  47. TQ3Status SubmitScene( DocumentPtr theDocument ) 
  48. {        
  49.     TQ3Vector3D                globalScale;
  50.     TQ3Vector3D                globalTranslate;
  51.     
  52.     globalScale.x = globalScale.y = globalScale.z = theDocument->fGroupScale;
  53.     globalTranslate = *(TQ3Vector3D *)&theDocument->fGroupCenter;
  54.     Q3Vector3D_Scale(&globalTranslate, -1, &globalTranslate);
  55.     Q3Style_Submit(theDocument->fInterpolation, theDocument->fView);
  56.     Q3Style_Submit(theDocument->fBackFacing , theDocument->fView);
  57.     Q3Style_Submit(theDocument->fFillStyle, theDocument->fView);
  58.         
  59.     Q3MatrixTransform_Submit( &theDocument->fRotation, theDocument->fView);
  60.         
  61.     Q3ScaleTransform_Submit(&globalScale, theDocument->fView);
  62.     Q3TranslateTransform_Submit(&globalTranslate, theDocument->fView);
  63.     Q3DisplayGroup_Submit( theDocument->fModel, theDocument->fView);
  64.     
  65.     return kQ3Success ;
  66. }
  67.  
  68. //-----------------------------------------------------------------------------------------------
  69.  
  70. static TQ3Status GetDocumentGroupBoundingBox( 
  71.     DocumentPtr theDocument , 
  72.     TQ3BoundingBox *viewBBox)
  73. {
  74.     TQ3Status        status;
  75.     TQ3ViewStatus    viewStatus ;
  76.     
  77.     status = Q3View_StartBoundingBox( theDocument->fView, kQ3ComputeBoundsApproximate );
  78.     do {
  79.         status = SubmitScene( theDocument ) ;
  80.     } while((viewStatus = Q3View_EndBoundingBox( theDocument->fView, viewBBox )) == kQ3ViewStatusRetraverse );
  81.     return status ;
  82. }
  83.  
  84. //-----------------------------------------------------------------------------------------------
  85.  
  86. TQ3ViewObject MyNewView(WindowPtr theWindow)
  87. {
  88.     TQ3Status                myStatus;
  89.     TQ3ViewObject            myView;
  90.     TQ3DrawContextObject        myDrawContext;
  91.     TQ3RendererObject        myRenderer;
  92.     TQ3CameraObject            myCamera;
  93.     TQ3GroupObject            myLights;
  94.     
  95.     myView = Q3View_New();
  96.     
  97.     //    Create and set draw context.
  98.     if ((myDrawContext = MyNewDrawContext(theWindow)) == nil )
  99.         goto bail;
  100.         
  101.     if ((myStatus = Q3View_SetDrawContext(myView, myDrawContext)) == kQ3Failure )
  102.         goto bail;
  103.  
  104.     Q3Object_Dispose( myDrawContext ) ;
  105.     
  106.     // Create and set renderer.
  107.     //
  108.     // hacky way to do this, but since I wanted these snippets to have 
  109.     // a minimal interface, this will suffice
  110.     //
  111.     // change the next line to “#if 1” to use the WF renderer
  112.     
  113. #if 0
  114.     // this would use the wireframe renderer
  115.     myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeWireFrame);
  116.     if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  117.         goto bail;
  118.     }
  119. #else
  120.     // this would use the interactive software renderer
  121.  
  122.     if ((myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeInteractive)) != nil ) {
  123.         if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  124.             goto bail;
  125.         }
  126.     }
  127.     else {
  128.         goto bail;
  129.     }
  130. #endif
  131.  
  132.     Q3Object_Dispose( myRenderer ) ;
  133.     
  134.     //    Create and set camera.
  135.     if ( (myCamera = MyNewCamera(theWindow)) == nil )
  136.         goto bail;
  137.         
  138.     if ((myStatus = Q3View_SetCamera(myView, myCamera)) == kQ3Failure )
  139.         goto bail;
  140.  
  141.     Q3Object_Dispose( myCamera ) ;
  142.     
  143.     //    Create and set lights.
  144.     if ((myLights = MyNewLights()) == nil )
  145.         goto bail;
  146.         
  147.     if ((myStatus = Q3View_SetLightGroup(myView, myLights)) == kQ3Failure )
  148.         goto bail;
  149.         
  150.     Q3Object_Dispose(myLights);
  151.  
  152.     //    Done!!!
  153.     return ( myView );
  154.     
  155. bail:
  156.     //    If any of the above failed, then don't return a view.
  157.     return ( nil );
  158. }
  159.  
  160. //----------------------------------------------------------------------------------
  161.  
  162. TQ3DrawContextObject MyNewDrawContext(WindowPtr theWindow)
  163. {
  164.     TQ3DrawContextData        myDrawContextData;
  165.     TQ3MacDrawContextData    myMacDrawContextData;
  166.     TQ3ColorARGB            ClearColor;
  167.     TQ3DrawContextObject    myDrawContext ;
  168.     
  169.     ClearColor.a = 1.0;
  170.     ClearColor.r = 1.0;
  171.     ClearColor.g = 1.0;
  172.     ClearColor.b = 1.0;
  173.     
  174.     //    Fill in draw context data.
  175.     myDrawContextData.clearImageMethod = kQ3ClearMethodWithColor;
  176.     myDrawContextData.clearImageColor = ClearColor;
  177.     
  178.     myDrawContextData.paneState = kQ3False;
  179.     myDrawContextData.maskState = kQ3False;
  180.     
  181.     myDrawContextData.doubleBufferState = kQ3True;
  182.  
  183.     myMacDrawContextData.drawContextData = myDrawContextData;
  184.     
  185.     myMacDrawContextData.window = (CGrafPtr) theWindow;        // this is the window associated with the view
  186.     myMacDrawContextData.library = kQ3Mac2DLibraryNone;
  187.     myMacDrawContextData.viewPort = nil;
  188.     myMacDrawContextData.grafPort = nil;
  189.     
  190.     //    Create draw context and return it, if it’s nil the caller must handle
  191.     myDrawContext = Q3MacDrawContext_New(&myMacDrawContextData) ;
  192.  
  193.     return myDrawContext ;
  194. }
  195.  
  196. //----------------------------------------------------------------------------------
  197.  
  198. TQ3CameraObject MyNewCamera(WindowPtr theWindow)
  199. {
  200.     TQ3CameraObject                    myCamera;
  201.     TQ3CameraData                    myCameraData;
  202.     TQ3ViewAngleAspectCameraData        myViewAngleCameraData;
  203.     TQ3Point3D                        cameraFrom     = { 0.0, 0.0, 30.0 };
  204.     TQ3Point3D                        cameraTo     = { 0.0, 0.0, 0.0 };
  205.     TQ3Vector3D                        cameraUp     = { 0.0, 1.0, 0.0 };
  206.     
  207.     float                             fieldOfView = .52359333333;
  208.     float                             hither         = 0.001;
  209.     float                             yon         = 1000;
  210.     
  211.     //    Fill in camera data.
  212.     myCameraData.placement.cameraLocation = cameraFrom;
  213.     myCameraData.placement.pointOfInterest = cameraTo;
  214.     myCameraData.placement.upVector = cameraUp;
  215.     
  216.     myCameraData.range.hither = hither;
  217.     myCameraData.range.yon = yon;
  218.     
  219.     myCameraData.viewPort.origin.x = -1.0;
  220.     myCameraData.viewPort.origin.y = 1.0;
  221.     myCameraData.viewPort.width = 2.0;
  222.     myCameraData.viewPort.height = 2.0;
  223.     
  224.     myViewAngleCameraData.cameraData = myCameraData;
  225.     myViewAngleCameraData.fov = fieldOfView ;
  226.     
  227.     // set up the aspect ratio based on the window
  228.     myViewAngleCameraData.aspectRatioXToY =  
  229.             (float) (theWindow->portRect.right - theWindow->portRect.left) / 
  230.             (float) (theWindow->portRect.bottom - theWindow->portRect.top);
  231.  
  232.     myCamera = Q3ViewAngleAspectCamera_New(&myViewAngleCameraData);    
  233.     
  234.     //    Return the camera.
  235.     return ( myCamera );
  236. }
  237.  
  238.  
  239. //----------------------------------------------------------------------------------
  240.  
  241. TQ3GroupObject MyNewLights()
  242. {
  243.     TQ3GroupPosition            myGroupPosition;
  244.     TQ3GroupObject            myLightList;
  245.     TQ3LightData                myLightData;
  246.     TQ3PointLightData        myPointLightData;
  247.     TQ3DirectionalLightData    myDirectionalLightData;
  248.     TQ3LightObject            myAmbientLight, myPointLight, myFillLight;
  249.     TQ3Point3D                pointLocation = { -10.0, 0.0, 10.0 };
  250.     TQ3Vector3D                fillDirection = { 10.0, 0.0, 10.0 };
  251.     TQ3ColorRGB                WhiteLight = { 1.0, 1.0, 1.0 };
  252.     
  253.     //    Set up light data for ambient light.  This light data will be used for point and fill
  254.     //    light also.
  255.  
  256.     myLightData.isOn = kQ3True;
  257.     myLightData.color = WhiteLight;
  258.     
  259.     //    Create ambient light.
  260.     myLightData.brightness = .2;
  261.     myAmbientLight = Q3AmbientLight_New(&myLightData);
  262.     if ( myAmbientLight == nil )
  263.         goto bail;
  264.     
  265.     //    Create point light.
  266.     myLightData.brightness = 1.0;
  267.     myPointLightData.lightData = myLightData;
  268.     myPointLightData.castsShadows = kQ3False;
  269.     myPointLightData.attenuation = kQ3AttenuationTypeNone;
  270.     myPointLightData.location = pointLocation;
  271.     myPointLight = Q3PointLight_New(&myPointLightData);
  272.     if ( myPointLight == nil )
  273.         goto bail;
  274.  
  275.     //    Create fill light.
  276.     myLightData.brightness = .2;
  277.     myDirectionalLightData.lightData = myLightData;
  278.     myDirectionalLightData.castsShadows = kQ3False;
  279.     myDirectionalLightData.direction = fillDirection;
  280.     myFillLight = Q3DirectionalLight_New(&myDirectionalLightData);
  281.     if ( myFillLight == nil )
  282.         goto bail;
  283.  
  284.     //    Create light group and add each of the lights into the group.
  285.     myLightList = Q3LightGroup_New();
  286.     if ( myLightList == nil )
  287.         goto bail;
  288.     myGroupPosition = Q3Group_AddObject(myLightList, myAmbientLight);
  289.     if ( myGroupPosition == 0 )
  290.         goto bail;
  291.     myGroupPosition = Q3Group_AddObject(myLightList, myPointLight);
  292.     if ( myGroupPosition == 0 )
  293.         goto bail;
  294.     myGroupPosition = Q3Group_AddObject(myLightList, myFillLight);
  295.     if ( myGroupPosition == 0 )
  296.         goto bail;
  297.  
  298.     Q3Object_Dispose( myAmbientLight ) ;
  299.     Q3Object_Dispose( myPointLight ) ;
  300.     Q3Object_Dispose( myFillLight ) ;
  301.  
  302.     //    Done!
  303.     return ( myLightList );
  304.     
  305. bail:
  306.     //    If any of the above failed, then return nothing!
  307.     return ( nil );
  308. }
  309.  
  310. //----------------------------------------------------------------------------------
  311.  
  312. TQ3GroupObject MyNewModelFromFile(FSSpec *theFileSpec)
  313. {
  314.     TQ3GroupObject        myGroup = NULL ;
  315.     TQ3Boolean            isText = kQ3False ;
  316.     TQ3FileMode            myFileMode = 0;
  317.     TQ3FileObject        theFile;
  318.     
  319.     //    Create a ordered group for the complete model.
  320.     if ((myGroup = Q3DisplayGroup_New()) == NULL )
  321.         return NULL;
  322.         
  323.     MyAddShaderToGroup( myGroup ) ;
  324.  
  325.     theFile = MyGetNewFile( theFileSpec, &isText ) ;
  326.     
  327.     if( isText == kQ3True )
  328.         myFileMode |= kQ3FileModeText;    // is it a text metafile??    
  329.  
  330.     // Open the file object
  331.     if( Q3File_OpenRead( theFile, &myFileMode ) != kQ3Success)
  332.         return  NULL ;
  333.  
  334.     if( MyReadModelFromFile( theFile, myGroup ) == 0)
  335.         DebugStr("\pMetafile data read is null") ;
  336.     
  337.     Q3File_Close(theFile);            // close and dispose of the file object
  338.     Q3Object_Dispose(theFile);
  339.     
  340.     return myGroup ;
  341. }
  342.  
  343.  
  344. //----------------------------------------------------------------------------------
  345. // attach a shader to the group
  346.  
  347. TQ3Status MyAddShaderToGroup( TQ3GroupObject group )
  348. {
  349.     TQ3ShaderObject    illuminationShader = Q3PhongIllumination_New();
  350.  
  351.     Q3Group_AddObject(group, illuminationShader);
  352.     Q3Object_Dispose(illuminationShader);
  353.     return(kQ3Success);
  354. }
  355.  
  356. //----------------------------------------------------------------------------------
  357. // read model from file object into the supplied group
  358.  
  359. TQ3Status MyReadModelFromFile( TQ3FileObject theFile,TQ3GroupObject myGroup)
  360. {    
  361.     if(theFile != NULL) {
  362.     
  363.         TQ3Object            myTempObj ;
  364.         TQ3Boolean            isEOF ;
  365.                 
  366.     
  367.         // read objects from the file
  368.         do {
  369.         
  370.             myTempObj = Q3File_ReadObject( theFile );
  371.             
  372.             if( myTempObj != NULL ) {
  373.                 // we only want the object in our main group if we can draw it
  374.                 if ( Q3Object_IsDrawable( myTempObj) ) 
  375.                     Q3Group_AddObject( myGroup, myTempObj ) ;
  376.                 
  377.                 // we either added the object to the main group, or we don't care
  378.                 // so we can safely dispose of the object
  379.                 Q3Object_Dispose( myTempObj ) ;
  380.             }
  381.             
  382.             // check to see if we reached the end of file yet
  383.             isEOF = Q3File_IsEndOfFile( theFile );
  384.             
  385.         } while (isEOF == kQ3False);    
  386.     }
  387.     
  388.     if( myGroup != NULL )
  389.         return kQ3Success ;
  390.     else
  391.         return kQ3Failure ;
  392. }
  393.  
  394. //-----------------------------------------------------------------------------------------------
  395. // cleaned up from IM QuickDraw 3D pp 15-5
  396. static TQ3FileObject MyGetNewFile( FSSpec *myFSSpec, TQ3Boolean *isText )
  397. {
  398.     TQ3FileObject        myFileObj;
  399.     TQ3StorageObject        myStorageObj;
  400.     OSType                myFileType;
  401.     
  402.     FInfo                fndrInfo ;
  403.  
  404.     // we assume the FSSpec passed in was valid, get the file information
  405.     // we need to know the file type, this routine may get called by an appleEvent
  406.     // handler, so we can't assume a type, we need to get it from the fsspec.
  407.     
  408.     FSpGetFInfo( myFSSpec, &fndrInfo ) ;
  409.     
  410.     // pull out the file type
  411.     
  412.     myFileType = fndrInfo.fdType ;
  413.     
  414.     // Create new storage object and new file object 
  415.     if(((myStorageObj = Q3FSSpecStorage_New( myFSSpec )) == NULL) 
  416.         || ((myFileObj = Q3File_New()) == NULL)) 
  417.     {
  418.         if (myStorageObj != NULL) 
  419.             Q3Object_Dispose(myStorageObj);
  420.         return(NULL);
  421.     }
  422.  
  423.     // Set the storage for the file object
  424.     Q3File_SetStorage(myFileObj, myStorageObj);
  425.     Q3Object_Dispose(myStorageObj);
  426.  
  427.     if (myFileType == '3DMF')
  428.         *isText = kQ3False ;
  429.     else if (myFileType == 'TEXT')
  430.         *isText = kQ3True ;
  431.  
  432.     return (myFileObj);
  433. }
  434.  
  435.  
  436. //-------------------------------------------------------------------------------------------
  437. //
  438. Boolean MetafileFileSpecify( FSSpec *theFile )
  439. {
  440.     StandardFileReply    theSFReply ;
  441.     SFTypeList            myTypes = { '3DMF' } ;
  442.     const short            numTypes = 1 ;
  443.         
  444.     // Get the file name to open
  445.     StandardGetFile( nil, numTypes, myTypes, &theSFReply ) ;
  446.     
  447.     if( theSFReply.sfGood )
  448.         *theFile = theSFReply.sfFile ;
  449.     
  450.     // did the user cancel?
  451.     return theSFReply.sfGood ;
  452.     
  453. }
  454. //----------------------------------------------------------------------------------
  455.  
  456.  
  457. void GetGroupBBox(
  458.     DocumentPtr            theDocument,
  459.     TQ3BoundingBox         *viewBBox)
  460. {
  461.     TQ3Point3D                     from     = { 0.0, 0.0, 1.0 };
  462.     TQ3Point3D                     to         = { 0.0, 0.0, 0.0 };
  463.     TQ3Vector3D                 up         = { 0.0, 1.0, 0.0 };
  464.     
  465.     float                         fieldOfView = .52359333333;
  466.     float                         hither         =  0.5;
  467.     float                         yon         =  1.5;
  468.     TQ3GroupObject                mainGroup = theDocument->fModel ;
  469.  
  470.     TQ3Status                    status;
  471.     
  472. #ifdef BETA_1_BUILD
  473.     Q3View_StartBounds( theDocument->fView );
  474.  
  475.     status = Q3DisplayGroup_BoundingBox(mainGroup, 
  476.                                         viewBBox, 
  477.                                         kQ3ComputeBoundsApproximate,
  478.                                          viewObject);
  479.  
  480.     Q3View_EndBounds( theDocument->fView );
  481. #else
  482.     status = GetDocumentGroupBoundingBox( theDocument , viewBBox) ;
  483. #endif
  484.                                         
  485.     //
  486.     //  If we have a point model, then the "viewBBox" would end up
  487.     //  being a "singularity" at the location of the point.  As
  488.     //  this bounding "box" is used in setting up the camera spec,
  489.     //  we get bogus input into Escher.
  490.     
  491.     {
  492.          float        xSize, ySize, zSize;
  493.         
  494.         xSize = viewBBox->max.x - viewBBox->min.x;
  495.         ySize = viewBBox->max.y - viewBBox->min.y;
  496.         zSize = viewBBox->max.z - viewBBox->min.z;
  497.  
  498.         if (xSize <= kQ3RealZero &&
  499.             ySize <= kQ3RealZero &&
  500.             zSize <= kQ3RealZero) {
  501.             
  502.             viewBBox->max.x += 0.0001;
  503.             viewBBox->max.y += 0.0001;
  504.             viewBBox->max.z += 0.0001;
  505.             
  506.             viewBBox->min.x -= 0.0001;
  507.             viewBBox->min.y -= 0.0001;
  508.             viewBBox->min.z -= 0.0001;
  509.         }
  510.     }
  511. }
  512.  
  513.  
  514.  
  515.  
  516. //------------------------------------------------------------------------
  517.  
  518.  
  519. TQ3Point3D AdjustCamera(
  520.     DocumentPtr            theDocument,
  521.     short                winWidth,
  522.     short                winHeight)
  523. {
  524.     float                         fieldOfView;
  525.     float                         hither;
  526.     float                         yon;
  527.     TQ3CameraPlacement            placement;
  528.     TQ3CameraRange                range;
  529.     TQ3BoundingBox                 viewBBox;
  530.     long                         fromAxis;    
  531.     float                         maxDimension;
  532.      float                        xSize, ySize, zSize;
  533.     float                        weights[2] = { 0.5, 0.5 };
  534.     TQ3Point3D                    points[2];
  535.     TQ3Vector3D                     viewVector;
  536.     TQ3Vector3D                    normViewVector;
  537.     TQ3Vector3D                    eyeToFrontClip;
  538.     TQ3Vector3D                    eyeToBackClip;
  539.     float                        viewDistance;
  540.     TQ3Vector3D                    diagonalVector;
  541.     float                        ratio;
  542.     TQ3CameraObject                camera;
  543.     
  544.     TQ3ViewObject                theView = theDocument->fView ;
  545.     TQ3GroupObject                mainGroup = theDocument->fModel ;
  546.     
  547.     TQ3Point3D                    *documentGroupCenter = &theDocument->fGroupCenter ;
  548.     float                        *documentGroupScale  = &theDocument->fGroupScale ;
  549.  
  550.     Q3View_GetCamera( theView, &camera);
  551.     GetGroupBBox( theDocument, &viewBBox);
  552.  
  553.     /*
  554.      *  If we have a point model, then the "viewBBox" would end up
  555.      *  being a "singularity" at the location of the point.  As
  556.      *  this bounding "box" is used in setting up the camera spec,
  557.      *  we get bogus input into Escher.
  558.      */
  559.     xSize = viewBBox.max.x - viewBBox.min.x;
  560.     ySize = viewBBox.max.y - viewBBox.min.y;
  561.     zSize = viewBBox.max.z - viewBBox.min.z;
  562.  
  563.     if (xSize <= kQ3RealZero &&
  564.         ySize <= kQ3RealZero &&
  565.         zSize <= kQ3RealZero)  {
  566.         viewBBox.max.x += 0.0001;
  567.         viewBBox.max.y += 0.0001;
  568.         viewBBox.max.z += 0.0001;
  569.         
  570.         viewBBox.min.x -= 0.0001;
  571.         viewBBox.min.y -= 0.0001;
  572.         viewBBox.min.z -= 0.0001;
  573.     }
  574.  
  575.     points[0] = viewBBox.min;
  576.     points[1] = viewBBox.max;
  577.  
  578.     Q3Point3D_AffineComb(points, weights, 2, documentGroupCenter);
  579.  
  580.     /*
  581.      *  The "from" point is on a vector perpendicular to the plane
  582.      *  in which the bounding box has greatest dimension.  As "up" is
  583.      *  always in the positive y direction, look at x and z directions.
  584.      */
  585.     xSize = viewBBox.max.x - viewBBox.min.x;
  586.     zSize = viewBBox.max.z - viewBBox.min.z;
  587.     
  588.     if (xSize > zSize) {
  589.         fromAxis = kQ3AxisZ;
  590.     } else {
  591.         fromAxis = kQ3AxisX;
  592.     }
  593.  
  594.     /*
  595.      *  Compute the length of the diagonal of the bounding box.
  596.      *
  597.      *  The hither and yon planes are adjusted so that the
  598.       *  diagonal of the bounding box is 7/8 the size of the
  599.       *  minimum dimension of the view frustum. The diagonal is used instead
  600.       *  of the maximum size (in x, y, or z) so that when you rotate
  601.       *  the object, the corners don't get clipped out.
  602.       */
  603.     Q3Point3D_Subtract(
  604.         &viewBBox.max,
  605.         &viewBBox.min,
  606.         &diagonalVector);
  607.  
  608.     maxDimension    =    Q3Vector3D_Length(&diagonalVector);
  609.     maxDimension    *=    8.0 / 7.0;
  610.     
  611.     ratio = 1.0 / maxDimension;
  612.             
  613.     *documentGroupScale = ratio;
  614.     
  615.     Q3Camera_GetPlacement(camera, &placement);
  616.  
  617.     Q3Point3D_Subtract(
  618.         &placement.cameraLocation,
  619.         &placement.pointOfInterest,
  620.         &viewVector);
  621.         
  622.     viewDistance = Q3Vector3D_Length(&viewVector);
  623.     
  624.     Q3Vector3D_Normalize(&viewVector, &normViewVector);
  625.     
  626.     Q3Vector3D_Scale(&normViewVector, 
  627.                      viewDistance - ratio * maxDimension/2.0,
  628.                      &eyeToFrontClip);
  629.                     
  630.     Q3Vector3D_Scale(&normViewVector, 
  631.                     viewDistance + ratio * maxDimension/2.0,
  632.                     &eyeToBackClip);
  633.  
  634.     hither     = Q3Vector3D_Length(&eyeToFrontClip);
  635.     yon     = Q3Vector3D_Length(&eyeToBackClip);
  636.     
  637.     fieldOfView = 2 * atan((ratio * maxDimension/2.0)/hither);
  638.  
  639.     range.hither                 = hither;
  640.     range.yon                     = yon;
  641.  
  642.     Q3Camera_SetRange(camera, &range);
  643.  
  644.     Q3ViewAngleAspectCamera_SetFOV(
  645.         camera, fieldOfView);
  646.  
  647.     Q3ViewAngleAspectCamera_SetAspectRatio(
  648.         camera, (float) winWidth / (float) winHeight);
  649.  
  650.     Q3Object_Dispose(camera);
  651.     
  652.     return( *documentGroupCenter );
  653. }
  654.  
  655.  
  656.