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