|
|
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.