home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / winui / controls / spincube / paint.c < prev    next >
C/C++ Source or Header  |  1997-10-05  |  20KB  |  554 lines

  1. /******************************************************************************\
  2. *
  3. *  MODULE:      PAINT.C
  4. *
  5. *  PURPOSE:     This is the module responsible for painting the SPINCUBE
  6. *               custom control. When Paint() is called we retrieve a
  7. *               pointer to a SPINCUBEINFO structure, and then use it's
  8. *               current rotation & translation values to transform the
  9. *               polyhedron described by gNormalizedVertices & gaiFacets.
  10. *               Once we've transformed the vertices, we draw the
  11. *               background, which consists of a grey rectangle and a few
  12. *               black lines (a crass attempt to render a perspective
  13. *               view into a "room"), on the offscreen bitmap associated
  14. *               with the control (i.e. pSCI->hbmCompat). Then we walk the
  15. *               facet list of the transformed polyhedron (gXformedVertices
  16. *               & gaiFacets), drawing only those facets whose outward
  17. *               normal faces us (again, drawing on pSCI->hbmCompat).
  18. *               Finally, we BitBlt the appropriate rectangle from our
  19. *               offscreen bitmap to the screen itself.
  20. *
  21. *               Drawing to the offscreen bitmap has two advantages over
  22. *               drawing straight to the screen:
  23. *
  24. *                 1. The actual drawing the user sees consists of only
  25. *                    a single BitBlt. Otherwise, the user would see us
  26. *                    both erase the polyhedron in it's old position and
  27. *                    draw it in it's new position (alot of flashing- not
  28. *                    very smooth animation).
  29. *
  30. *                 2. When a spincube control with the SS_ERASE style
  31. *                    is brought to the foreground, all it's contents
  32. *                    i.e. the cube trails) are saved & can be re-Blted
  33. *                    to the screen. Otherwise, all this info would be
  34. *                    lost & there'd be a big blank spot in the middle
  35. *                    of the control!
  36. *
  37. *               Interested persons should consult a text on 3 dimensional
  38. *               graphics for more information (i.e. "Computer Graphics:
  39. *               Principles and Practice", by Foley & van Dam).
  40. *
  41. *               Notes:
  42. *
  43. *               - A 3x2 tranformation matrix  is used instead of a  3x3
  44. *                 matrix, since the transformed z-values aren't needed.
  45. *                 (Normally these would be required for use in depth
  46. *                 sorting  [for hidden surface removal], but  since we
  47. *                 draw  only  a single convex polyhedron this  is not
  48. *                 necessary.)
  49. *
  50. *               - A simplified perspective viewing transformation
  51. *                 (which also  precludes the need for the transformed z
  52. *                 coordinates). In a nutshell, the perspective  scale
  53. *                 is as follows:
  54. *
  55. *                                    p' = S    x  p
  56. *                                          per
  57. *
  58. *                 where:
  59. *                        S    = WindowDepth /
  60. *                         per      (WindowDepth + fCurrentZTranslation)
  61. *
  62. *                 (WindowDepth is  the greater of the  control's window
  63. *                 height or window width.)
  64. *
  65. *
  66. *  FUNCTIONS:   Paint()                         - the paint routine
  67. *               TransformVertices()             - transforms vertices
  68. *               ComputeRotationTransformation() - computes xformation
  69. *                                                 based on current x, y
  70. *                                                 and z rotation angles
  71. *
  72. *
  73. *                           Microsoft Developer Support
  74. *                  Copyright (c) 1992-1997 Microsoft Corporation
  75. *
  76. \******************************************************************************/
  77.  
  78. #include <windows.h>
  79. #include <math.h>
  80. #include <stdlib.h>
  81. #include "spincube.h"
  82. #include "paint.h"
  83.  
  84.  
  85.  
  86. /******************************************************************************\
  87. *
  88. *  FUNCTION:     Paint
  89. *
  90. *  INPUTS:       hwnd - Handle of the window to draw into.
  91. *
  92. *  COMMENTS:     Draws window background & a polyhedron in the window.
  93. *
  94. \******************************************************************************/
  95.  
  96. void Paint (HWND hwnd)
  97. {
  98.   PSPINCUBEINFO  pSCI;
  99.   RECT           rect;
  100.   int            i;
  101.   LONG           lScaleFactor;
  102.   PAINTSTRUCT    ps;
  103.   HRGN           hrgnClip;
  104.   HBRUSH         hBrush, hBrushSave;
  105.   int            iX, iY, iCX, iCY;
  106.   int            facetIndex, numPoints;
  107.   POINT          polygn[MAX_POINTS_PER_FACET];
  108.   POINT          vector1, vector2;
  109.   COLORREF       acrColor[6] = { 0x0000ff, 0x00ff00, 0xff0000,
  110.                                  0x00ffff, 0xff00ff, 0xffff00 };
  111.  
  112.   pSCI = (PSPINCUBEINFO) GetWindowLong (hwnd, GWL_SPINCUBEDATA);
  113.  
  114.   BeginPaint (hwnd, &ps);
  115.  
  116.   if (memcmp((void *)&ps.rcPaint, (void *)&pSCI->rcCubeBoundary, sizeof(RECT))
  117.       & !REPAINT_BKGND(pSCI))
  118.  
  119.   {
  120.     //
  121.     // We're not here because it's time to animate (i.e. this paint isn't
  122.     //   the result of a WM_TIMER), so just do the Blt & blow out of here...
  123.     //
  124.  
  125.     BitBlt (ps.hdc,
  126.             ps.rcPaint.left,
  127.             ps.rcPaint.top,
  128.             ps.rcPaint.right - ps.rcPaint.left,
  129.             ps.rcPaint.bottom - ps.rcPaint.top,
  130.             pSCI->hdcCompat, ps.rcPaint.left,
  131.             ps.rcPaint.top, SRCCOPY);
  132.  
  133.     EndPaint (hwnd, &ps);
  134.     return;
  135.   }
  136.  
  137.  
  138.   //
  139.   // The rectangle we get back is in Desktop coordinates, so we need to
  140.   //   modify it to reflect coordinates relative to this window.
  141.   //
  142.  
  143.   GetWindowRect (hwnd, &rect);
  144.  
  145.   rect.right  -= rect.left;
  146.   rect.bottom -= rect.top;
  147.   rect.left = rect.top = 0;
  148.  
  149.  
  150.   //
  151.   // Determine a "best fit" scale factor for our polyhedron
  152.   //
  153.  
  154.   if (!(lScaleFactor = rect.right > rect.bottom ?
  155.                        rect.bottom/12 : rect.right/12))
  156.  
  157.     lScaleFactor = 1;
  158.  
  159.  
  160.   TransformVertices (hwnd, &rect, pSCI, lScaleFactor);
  161.  
  162.  
  163.   //
  164.   // Draw the window frame & background
  165.   //
  166.   // Note: The chances are that we are coming through here because we
  167.   //   got a WM_TIMER message & it's time to redraw the cube to simulate
  168.   //   animation. In that case all we want to erase/redraw is that small
  169.   //   rectangle which bounded the polyhedron the last time. The less
  170.   //   drawing that actually gets done the better, since we wnat to
  171.   //   minimize the flicker on the screen. __BeginPaint__ is perfect for
  172.   //   this because it causes all drawing outside of the invalid region
  173.   //   to be "clipped" (no drawing is performed outside of the invalid
  174.   //   region), and it also validates the invalid region.
  175.   //
  176.  
  177.   if (DO_ERASE(hwnd) || REPAINT_BKGND(pSCI))
  178.   {
  179.     hrgnClip = CreateRectRgnIndirect (&ps.rcPaint);
  180.     SelectClipRgn (pSCI->hdcCompat, hrgnClip);
  181.     DeleteObject (hrgnClip);
  182.  
  183.     SelectObject (pSCI->hdcCompat, GetStockObject (GRAY_BRUSH));
  184.     Rectangle    (pSCI->hdcCompat, (int)rect.left, (int)rect.top,
  185.                   (int)rect.right, (int)rect.bottom);
  186.  
  187.     iX = (rect.right  - rect.left) / 4;
  188.     iY = (rect.bottom - rect.top ) / 4;
  189.  
  190.     MoveToEx (pSCI->hdcCompat, (int)rect.left, (int)rect.top, NULL);
  191.     LineTo   (pSCI->hdcCompat, (int)rect.left + iX, (int)rect.top + iY);
  192.     LineTo   (pSCI->hdcCompat, (int)rect.left + iX, (int)rect.bottom - iY);
  193.     LineTo   (pSCI->hdcCompat, (int)rect.left,      (int)rect.bottom);
  194.  
  195.     MoveToEx (pSCI->hdcCompat, (int)rect.right, (int)rect.top, NULL);
  196.     LineTo   (pSCI->hdcCompat, (int)rect.right - iX, (int)rect.top + iY);
  197.     LineTo   (pSCI->hdcCompat, (int)rect.right - iX, (int)rect.bottom- iY);
  198.     LineTo   (pSCI->hdcCompat, (int)rect.right,      (int)rect.bottom);
  199.  
  200.     MoveToEx (pSCI->hdcCompat, (int)rect.left + iX,  (int)rect.top + iY, NULL);
  201.     LineTo   (pSCI->hdcCompat, (int)rect.right - iX, (int)rect.top + iY);
  202.  
  203.     MoveToEx (pSCI->hdcCompat, (int)rect.left + iX,  (int)rect.bottom - iY, NULL);
  204.     LineTo   (pSCI->hdcCompat, (int)rect.right - iX, (int)rect.bottom - iY);
  205.  
  206.     SelectClipRgn (pSCI->hdcCompat, NULL);
  207.  
  208.     pSCI->iOptions &= ~SPINCUBE_REPAINT_BKGND;
  209.   }
  210.  
  211.  
  212.   //
  213.   // Draw the polyhedron. We'll walk through the facets list and compute
  214.   //   the normal for each facet- if the normal has z > 0, then the facet
  215.   //   faces us and we'll draw it. Note that this algorithim is ONLY valid
  216.   //   for scenes with a single, convex polyhedron.
  217.   //
  218.   // Note: Use GetDC here because the above call to BeginPaint will
  219.   //   probably not give us a DC with access to as much real estate as
  220.   //   we'd like (we wouldn't be able to draw outside of the invalid
  221.   //   region). We can party on the entire control window with the DC
  222.   //   returned by GetDC.
  223.   //
  224.  
  225.   for (i = 0, facetIndex = 0; i < NUMFACETS; i++)
  226.   {
  227.     vector1.x = gXformedVertices[gaiFacets[facetIndex + 1]].x -
  228.                 gXformedVertices[gaiFacets[facetIndex]].x;
  229.     vector1.y = gXformedVertices[gaiFacets[facetIndex + 1]].y -
  230.                 gXformedVertices[gaiFacets[facetIndex]].y;
  231.     vector2.x = gXformedVertices[gaiFacets[facetIndex + 2]].x -
  232.                 gXformedVertices[gaiFacets[facetIndex + 1]].x;
  233.     vector2.y = gXformedVertices[gaiFacets[facetIndex + 2]].y -
  234.                 gXformedVertices[gaiFacets[facetIndex + 1]].y;
  235.  
  236.     for (numPoints = 0; gaiFacets[facetIndex] != -1; numPoints++, facetIndex++)
  237.     {
  238.       polygn[numPoints].x = gXformedVertices[gaiFacets[facetIndex]].x;
  239.       polygn[numPoints].y = gXformedVertices[gaiFacets[facetIndex]].y;
  240.     }
  241.  
  242.     facetIndex++; /* skip over the -1's in the facets list */
  243.     if ((vector1.x*vector2.y - vector1.y*vector2.x) > 0)
  244.     {
  245.       hBrush     = CreateSolidBrush (acrColor[i]);
  246.       hBrushSave = (HBRUSH) SelectObject (pSCI->hdcCompat, hBrush);
  247.  
  248.       Polygon (pSCI->hdcCompat, &polygn[0], numPoints);
  249.  
  250.       SelectObject (pSCI->hdcCompat, hBrushSave);
  251.       DeleteObject (hBrush);
  252.     }
  253.   }
  254.  
  255.   iX  = pSCI->rcCubeBoundary.left < ps.rcPaint.left ?
  256.         pSCI->rcCubeBoundary.left : ps.rcPaint.left;
  257.   iY  = pSCI->rcCubeBoundary.top  < ps.rcPaint.top  ?
  258.         pSCI->rcCubeBoundary.top  : ps.rcPaint.top;
  259.  
  260.   iCX = (pSCI->rcCubeBoundary.right > ps.rcPaint.right ?
  261.          pSCI->rcCubeBoundary.right : ps.rcPaint.right) - iX;
  262.  
  263.   iCY = (pSCI->rcCubeBoundary.bottom > ps.rcPaint.bottom ?
  264.          pSCI->rcCubeBoundary.bottom : ps.rcPaint.bottom) - iY;
  265.  
  266.   EndPaint (hwnd, &ps);
  267.  
  268.   ps.hdc = GetDC (hwnd);
  269.  
  270.   BitBlt (ps.hdc, iX, iY, iCX, iCY, pSCI->hdcCompat, iX, iY, SRCCOPY);
  271.  
  272.   ReleaseDC (hwnd, ps.hdc);
  273.  
  274. }
  275.  
  276.  
  277.  
  278. /******************************************************************************\
  279. *
  280. *  FUNCTION:     TransformVertices
  281. *
  282. *  INPUTS:       hwnd         - control window handle
  283. *                pWindowRect  - pointer to RECT describing control's dimensions
  284. *                pSCI         - pointer to control's SPINCUBEINFO structure
  285. *                fScaleFactor - scale factor for use in this window
  286. *
  287. \******************************************************************************/
  288.  
  289. void TransformVertices (HWND hwnd,          RECT  *pWindowRect,
  290.                         PSPINCUBEINFO pSCI, LONG  lScaleFactor)
  291. {
  292.   int    i;
  293.   int    iWindowDepth = pWindowRect->right > pWindowRect->bottom ?
  294.                         pWindowRect->right : pWindowRect->bottom;
  295.   RECT   WindowRect;
  296.   float  fDepthScale;
  297.   int    iNewTranslationInc = (rand() % 10) + 2;
  298.   float  fNewRotationInc    = (float) 0.01 * ((rand() % 30) + 2);
  299.  
  300.   WindowRect.left = - (WindowRect.right  = pWindowRect->right  >> 1);
  301.   WindowRect.top  = - (WindowRect.bottom = pWindowRect->bottom >> 1);
  302.  
  303.   //
  304.   // Initiailize the bounding rectangle with max/min vals
  305.   //
  306.  
  307.   pSCI->rcCubeBoundary.left   =
  308.   pSCI->rcCubeBoundary.top    = 100000; // big positive value
  309.   pSCI->rcCubeBoundary.right  =
  310.   pSCI->rcCubeBoundary.bottom = -100000; // small negative value
  311.  
  312.  
  313.   //
  314.   // First scale, then rotate, then translate each vertex.
  315.   //   Keep track of the maximum & minimum values bounding the
  316.   //   vertices in the x,y plane for use later in bounds checking.
  317.   //
  318.   // Note: we don't bother computing z values after the scale,
  319.   //   as they are only really necessary for the rotation. If we
  320.   //   were doing real bounds checking we'd need it, but this code
  321.   //   simply uses the pSCI->iCurrentZTranslation to determine
  322.   //   the z-boundaries.
  323.   //
  324.  
  325.   for (i = 0; i < NUMVERTICES; i++)
  326.   {
  327.     LONG tempX;
  328.  
  329.     //
  330.     // Copy the static vertices into a temp array
  331.     //
  332.  
  333.     gXformedVertices[i] = gNormalizedVertices[i];
  334.  
  335.     //
  336.     // The scale...
  337.     //
  338.  
  339.     gXformedVertices[i].x *= lScaleFactor;
  340.     gXformedVertices[i].y *= lScaleFactor;
  341.     gXformedVertices[i].z *= lScaleFactor;
  342.  
  343.     //
  344.     // The rotation...
  345.     //
  346.  
  347.     ComputeRotationTransformation (pSCI->fCurrentXRotation,
  348.                                    pSCI->fCurrentYRotation,
  349.                                    pSCI->fCurrentZRotation);
  350.  
  351.     tempX   =               (LONG) (gM[0][0] * gXformedVertices[i].x +
  352.                                     gM[0][1] * gXformedVertices[i].y +
  353.                                     gM[0][2] * gXformedVertices[i].z);
  354.  
  355.     gXformedVertices[i].y = (LONG) (gM[1][0] * gXformedVertices[i].x +
  356.                                     gM[1][1] * gXformedVertices[i].y +
  357.                                     gM[1][2] * gXformedVertices[i].z);
  358.     gXformedVertices[i].x = tempX;
  359.  
  360.     //
  361.     // The translation...
  362.     //
  363.  
  364.     gXformedVertices[i].x += pSCI->iCurrentXTranslation;
  365.     gXformedVertices[i].y += pSCI->iCurrentYTranslation;
  366.  
  367.     //
  368.     // Check if we have new max or min vals
  369.     //
  370.  
  371.     if (pSCI->rcCubeBoundary.left > gXformedVertices[i].x)
  372.  
  373.       pSCI->rcCubeBoundary.left = gXformedVertices[i].x;
  374.  
  375.     if (pSCI->rcCubeBoundary.right < gXformedVertices[i].x)
  376.  
  377.       pSCI->rcCubeBoundary.right = gXformedVertices[i].x;
  378.  
  379.     if (pSCI->rcCubeBoundary.top > gXformedVertices[i].y)
  380.  
  381.       pSCI->rcCubeBoundary.top = gXformedVertices[i].y;
  382.  
  383.     if (pSCI->rcCubeBoundary.bottom < gXformedVertices[i].y)
  384.  
  385.       pSCI->rcCubeBoundary.bottom = gXformedVertices[i].y;
  386.   }
  387.  
  388.  
  389.   //
  390.   // Now for some bounds checking, change translation & rotation increments
  391.   //   if we hit a "wall". Also so the gbHitBoundary flag so we remember
  392.   //   to flash the cube when we draw it.
  393.   //
  394.  
  395.   if (pSCI->rcCubeBoundary.left < WindowRect.left)
  396.   {
  397.     pSCI->iCurrentXTranslationInc = iNewTranslationInc;
  398.     pSCI->fCurrentZRotationInc    = fNewRotationInc;
  399.   }
  400.  
  401.   else if (pSCI->rcCubeBoundary.right > WindowRect.right)
  402.   {
  403.     pSCI->iCurrentXTranslationInc = -iNewTranslationInc;
  404.     pSCI->fCurrentZRotationInc    = -fNewRotationInc;
  405.   }
  406.  
  407.   if (pSCI->rcCubeBoundary.top < WindowRect.top)
  408.    {
  409.     pSCI->iCurrentYTranslationInc = iNewTranslationInc;
  410.     pSCI->fCurrentXRotationInc    = fNewRotationInc;
  411.   }
  412.  
  413.   else if (pSCI->rcCubeBoundary.bottom > WindowRect.bottom)
  414.   {
  415.     pSCI->iCurrentYTranslationInc = -iNewTranslationInc;
  416.     pSCI->fCurrentXRotationInc    = -fNewRotationInc;
  417.   }
  418.  
  419.   if (pSCI->iCurrentZTranslation < (int) lScaleFactor<<1)
  420.   {
  421.     pSCI->iCurrentZTranslationInc = iNewTranslationInc;
  422.     pSCI->fCurrentYRotationInc    = fNewRotationInc;
  423.   }
  424.  
  425.   else if (pSCI->iCurrentZTranslation > (iWindowDepth - (int) lScaleFactor))
  426.   {
  427.     pSCI->iCurrentZTranslationInc = -iNewTranslationInc;
  428.     pSCI->fCurrentYRotationInc    = -fNewRotationInc;
  429.   }
  430.  
  431.  
  432.   //
  433.   // Now a kludgy scale based on depth (iCurrentZTranslation) of the center
  434.   //   point of the polyhedron
  435.   //
  436.  
  437.   fDepthScale =  ((float) iWindowDepth) /
  438.                  ((float) (iWindowDepth + pSCI->iCurrentZTranslation));
  439.  
  440.   pSCI->rcCubeBoundary.left  = (LONG)(fDepthScale* pSCI->rcCubeBoundary.left  );
  441.   pSCI->rcCubeBoundary.right = (LONG)(fDepthScale* pSCI->rcCubeBoundary.right );
  442.   pSCI->rcCubeBoundary.top   = (LONG)(fDepthScale* pSCI->rcCubeBoundary.top   );
  443.   pSCI->rcCubeBoundary.bottom= (LONG)(fDepthScale* pSCI->rcCubeBoundary.bottom);
  444.  
  445.   for (i = 0; i < NUMVERTICES; i++)
  446.   {
  447.     gXformedVertices[i].x = (LONG) (fDepthScale * gXformedVertices[i].x);
  448.     gXformedVertices[i].y = (LONG) (fDepthScale * gXformedVertices[i].y);
  449.   }
  450.  
  451.  
  452.   //
  453.   // If currently in motion then increment the current rotation & tranlation
  454.   //
  455.  
  456.   if (IN_MOTION(hwnd))
  457.   {
  458.     pSCI->fCurrentXRotation += pSCI->fCurrentXRotationInc;
  459.     pSCI->fCurrentYRotation += pSCI->fCurrentYRotationInc;
  460.     pSCI->fCurrentZRotation += pSCI->fCurrentZRotationInc;
  461.  
  462.     pSCI->iCurrentXTranslation += pSCI->iCurrentXTranslationInc;
  463.     pSCI->iCurrentYTranslation += pSCI->iCurrentYTranslationInc;
  464.     pSCI->iCurrentZTranslation += pSCI->iCurrentZTranslationInc;
  465.   }
  466.  
  467.  
  468.   //
  469.   // Up to this point all coordinates are relative to a window whose
  470.   //   center is at (0,0). Now we'll translate appropriately...
  471.   //
  472.  
  473.   pSCI->rcCubeBoundary.left   += pWindowRect->right  >> 1;
  474.   pSCI->rcCubeBoundary.right  += pWindowRect->right  >> 1;
  475.   pSCI->rcCubeBoundary.top    += pWindowRect->bottom >> 1;
  476.   pSCI->rcCubeBoundary.bottom += pWindowRect->bottom >> 1;
  477.  
  478.   for (i = 0; i < NUMVERTICES; i++)
  479.   {
  480.     gXformedVertices[i].x += pWindowRect->right  >> 1;
  481.     gXformedVertices[i].y += pWindowRect->bottom >> 1;
  482.   }
  483.  
  484.  
  485.   //
  486.   // Since FillRect's are inclusive-exclusive (there'll be leftovers
  487.   //   from the last cube we drew otherwise)...
  488.   //
  489.  
  490.   pSCI->rcCubeBoundary.right++;
  491.   pSCI->rcCubeBoundary.bottom++;
  492.  
  493.  
  494.   //
  495.   // Finally, adjust the rcCubeBoundary such that it fits entirely within
  496.   //   the acutal control window. The reason for this is that when calling
  497.   //   InvalidateRect from SpincubeWndProc\case_WM_TIMER we may get
  498.   //   a different PAINSTRUCT.rcPaint (since InvalidateRect clips the passed
  499.   //   in rect to the window bounds) and our abouve test to memcmp() will
  500.   //   fail.
  501.   //
  502.  
  503.   if (pSCI->rcCubeBoundary.left < 0)
  504.  
  505.     pSCI->rcCubeBoundary.left = 0;
  506.  
  507.   if (pSCI->rcCubeBoundary.top < 0)
  508.  
  509.     pSCI->rcCubeBoundary.top = 0;
  510.  
  511.   if (pSCI->rcCubeBoundary.right > pWindowRect->right)
  512.  
  513.     pSCI->rcCubeBoundary.right = pWindowRect->right;
  514.  
  515.   if (pSCI->rcCubeBoundary.bottom > pWindowRect->bottom)
  516.  
  517.     pSCI->rcCubeBoundary.bottom = pWindowRect->bottom;
  518. }
  519.  
  520.  
  521. /******************************************************************************\
  522. *
  523. *  FUNCTION:    ComputeRotationTransformation
  524. *
  525. *  INPUTS:      fRotationX - Angle to rotate about X axis.
  526. *               fRotationY - Angle to rotate about Y axis.
  527. *               fRotationZ - Angle to rotate about Z axis.
  528. *
  529. *  COMMENTS:    Computes a 3x2 tranformation matrix which rotates about
  530. *               the Z axis, the Y axis, and the X axis, respectively.
  531. *
  532. \******************************************************************************/
  533.  
  534. void ComputeRotationTransformation (float fRotationX,
  535.                                     float fRotationY,
  536.                                     float fRotationZ)
  537. {
  538.   float sinX, cosX, sinY, cosY, sinZ, cosZ;
  539.  
  540.   sinX = (float) sin ((double) fRotationX);
  541.   cosX = (float) cos ((double) fRotationX);
  542.   sinY = (float) sin ((double) fRotationY);
  543.   cosY = (float) cos ((double) fRotationY);
  544.   sinZ = (float) sin ((double) fRotationZ);
  545.   cosZ = (float) cos ((double) fRotationZ);
  546.  
  547.   gM[0][0] =  cosY*cosZ;
  548.   gM[0][1] = -cosY*sinZ;
  549.   gM[0][2] =  sinY;
  550.   gM[1][0] =  sinX*sinY*cosZ + cosX*sinZ;
  551.   gM[1][1] = -sinX*sinY*sinZ + cosX*cosZ;
  552.   gM[1][2] = -sinX*cosY;
  553. }
  554.