home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / pctchnqs / 1991 / number6 / bezier / bez.c < prev    next >
Text File  |  1991-11-11  |  15KB  |  397 lines

  1. /* BEZ.C : Program to draw Bezier curves and their handles
  2.    interactively. User draws first handle by dragging, then second
  3.    handle; the Bezier curve rubber-bands together with the second
  4.    handle. Demonstrates the de Casteljau algorithm for fast
  5.    calculation of Bezier points.
  6.    Copyright (c) 1991, Michael A. Bertrand.   */
  7.  
  8. #include <windows.h>
  9. #include "bez.h"
  10.  
  11. HPEN   hRedPen;            /* red pen for handles. */
  12. int    LogPerDevice;       /* #logical units per device unit
  13.                               (both axes). */
  14. WORD   cxClient;           /* size of client area (x). */
  15. WORD   cyClient;           /* size of client area (y). */
  16. HANDLE hInst;              /* current instance */
  17. POINT  BezPts[NUM_BEZPTS]; /* array of pts along Bezier curve */
  18. POINT  *PtrBezPts;         /* pointer into BezPts[] array */
  19.  
  20. char Instr1[] =
  21.   "* Left Button down, drag, button up for 1st handle.";
  22. char Instr2[] =
  23.   "* Left Button down, drag, button up for 2nd handle and Bez.";
  24. char Instr3[] = "* Right click to clear window.";
  25.  
  26. int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
  27.            LPSTR lpszCmdLine, int nCmdShow)
  28. /*
  29.   USE:  Register window and set dispach message loop.
  30.   IN:   hInstance,hPrevInstance,lpszCmdLine,nCmdShow : standard WinMain parms
  31. */
  32. {
  33.   static char szAppName [] = "Bezier";
  34.   static char szIconName[] = "BezIcon";
  35.   static char szMenuName[] = "BezMenu";
  36.  
  37.   HWND     hWnd;    /* handle to WinMain's window */
  38.   MSG      msg;     /* message dispached to window */
  39.   WNDCLASS wc;      /* for registering window */
  40.  
  41.   /* Save instance handle in global var
  42.      so can use for "About" dialog box. */
  43.   hInst = hInstance;
  44.  
  45.   /* Register application window class. */
  46.   if (!hPrevInstance)
  47.     {
  48.     wc.style         = CS_HREDRAW | CS_VREDRAW;
  49.     wc.lpfnWndProc   = WndProc;  /* fn to get window's messages */
  50.     wc.cbClsExtra    = 0;
  51.     wc.cbWndExtra    = 0;
  52.     wc.hInstance     = hInstance;
  53.     wc.hIcon         = LoadIcon(hInstance, szIconName);
  54.     wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  55.     wc.hbrBackground = GetStockObject(WHITE_BRUSH);
  56.     wc.lpszMenuName  = szMenuName;  /* menu resource in RC file */
  57.     wc.lpszClassName = szAppName;   /* name used in call to CreateWindow() */
  58.  
  59.     if (!RegisterClass(&wc))
  60.       return(FALSE);
  61.     }
  62.  
  63.     /* Initialize specific instance. */
  64.     hWnd = CreateWindow(szAppName, szAppName, WS_OVERLAPPEDWINDOW,
  65.                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  66.                         CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
  67.  
  68.   ShowWindow(hWnd, nCmdShow); /* display the window */
  69.   UpdateWindow(hWnd);         /* update client area; send WM_PAINT */
  70.  
  71.   /* Read msgs from app que and dispatch them to appropriate win
  72.      function. Continues until GetMessage() returns NULL when it
  73.      receives WM_QUIT. */
  74.   while (GetMessage(&msg, NULL, NULL, NULL))
  75.     {
  76.     TranslateMessage(&msg);  /* process char input from keyboard */
  77.     DispatchMessage(&msg);   /* pass message to window function */
  78.     }
  79.   return(msg.wParam);
  80. }
  81.  
  82. long FAR PASCAL WndProc(HWND hWnd, unsigned iMessage,
  83.                         WORD wParam, LONG lParam)
  84. /*
  85.   USE: Application's window procedure : all app's messages come here.
  86.   IN:  hWnd,iMessage,wParam,lParam : standard Windows proc parameters
  87. */
  88. {
  89.   HDC         hDC;  /* must generate our own handle to DC to draw */
  90.   PAINTSTRUCT ps;         /* needed when receive WM_PAINT message */
  91.   FARPROC     lpProcAbout;      /* pointer to "AboutBez" function */
  92.  
  93.   switch(iMessage)
  94.     {
  95.     case WM_CREATE:
  96.       /* Create hRedPen once and store as global. */
  97.       hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  98.       break;  /* WM_CREATE */
  99.  
  100.     case WM_SIZE:
  101.       /* Get client area size into globals when window resized. */
  102.       cxClient = LOWORD(lParam);
  103.       cyClient = HIWORD(lParam);
  104.       break;  /* WM_SIZE */
  105.  
  106.     case WM_COMMAND:
  107.       if (wParam == IDM_ABOUT)
  108.         {
  109.         /* "About" menu item chosen by user :
  110.             call "AboutBez" function. */
  111.         lpProcAbout = MakeProcInstance(AboutBez, hInst);
  112.         DialogBox (hInst, "AboutBez", hWnd, lpProcAbout);
  113.         FreeProcInstance(lpProcAbout);
  114.         }
  115.       break;  /* WM_COMMAND */
  116.  
  117.     case WM_PAINT:
  118.       /* Repaint instructions at upper left of window. */
  119.       hDC = BeginPaint(hWnd, &ps);
  120.       SelectObject(hDC, GetStockObject(ANSI_VAR_FONT));
  121.       TextOut(hDC, 0,  0, Instr1, lstrlen(Instr1));
  122.       TextOut(hDC, 0, 15, Instr2, lstrlen(Instr2));
  123.       TextOut(hDC, 0, 30, Instr3, lstrlen(Instr3));
  124.       EndPaint(hWnd, &ps);
  125.       break;  /* WM_PAINT */
  126.  
  127.     case WM_LBUTTONDOWN:
  128.     case WM_RBUTTONDOWN:
  129.     case WM_MOUSEMOVE:
  130.     case WM_LBUTTONUP:
  131.       /* Mouse events passed on to BezTool() for processing. */
  132.       BezTool(hWnd, iMessage, lParam);
  133.       break;  /* WM_LBUTTONDOWN... */
  134.  
  135.     case WM_DESTROY:
  136.       /* Destroy window & delete pen when application terminated. */
  137.       DeleteObject(hRedPen);
  138.       PostQuitMessage(0);
  139.       break;  /* WM_DESTROY */
  140.     default:
  141.       return(DefWindowProc(hWnd, iMessage, wParam, lParam));
  142.     }  /* switch(iMessage) */
  143.   return(0L);
  144. }
  145.  
  146. void NEAR PASCAL BezTool(HWND hWnd, unsigned iMessage, LONG lParam)
  147. /*
  148. USE:  Process mouse event to draw handles and Bezier curve.
  149. IN:   hWnd     : handle to window
  150.       iMessage : mouse event (WM_LBUTTONDOWN, etc.)
  151.       lParam   : mouse coords (x == loword, y == hiword)
  152. NOTE: This is the interactive Bezier drawing tool which processes
  153. WM_RBUTTONDOWN, WM_LBUTTONDOWN, WM_MOUSEMOVE, and WM_LBUTTONUP
  154. messages. BezTool() is called repeatedly as the user draws. The
  155. current state of the tool is maintained in the key static variable
  156. iState. iState's value, as set last time thru the tool, determines
  157. the tool's action this time thru. Bezier control and handle points,
  158. as input by the user, are also maintained as statics so BezTool()
  159. remembers them the next time thru.
  160. */
  161. {
  162.   HDC    hDC;       /* must generate our own handle to DC to draw */
  163.   WORD   maxClient; /* larger of (cxClient, cyClient) */
  164.   POINT  inPt;      /* incoming point */
  165.   POINT  pts[2]; /* to get LogPerDevice, #logical units/dev. unit */
  166.   /* user-entered Bez control & handle (1st): */
  167.   static POINT ctrl1, hand1;
  168.   /* user-entered Bez control & handle (2nd): */
  169.   static POINT ctrl2, hand2;
  170.   static int iState;  /* BezTool()'s state : DRAG_HAND1, etc. */
  171.  
  172.   hDC = GetDC(hWnd);
  173.  
  174.   /* Set extents and origin so will be working
  175.      in range [-15000, +15000]. */
  176.   SetMapMode(hDC, MM_ISOTROPIC);
  177.   SetWindowExt(hDC, 30000, 30000);
  178.   maxClient = (cxClient > cyClient) ? cxClient : cyClient;
  179.   SetViewportExt(hDC, maxClient, -maxClient);
  180.   SetViewportOrg(hDC, cxClient >> 1, cyClient >> 1);
  181.  
  182.   /* Calculate #logical units per device unit --
  183.      will need later when draw little 3x3 boxes in DrawHandle(). */
  184.   pts[0].x = pts[0].y = 0;
  185.   pts[1].x = pts[1].y = 1;
  186.   DPtoLP(hDC, pts, 2);
  187.   LogPerDevice = (pts[1].x > pts[0].x) ? (pts[1].x - pts[0].x) :
  188.                                          (pts[0].x - pts[1].x);
  189.  
  190.   /* Incoming point in device coordinates. */
  191.   inPt.x = LOWORD(lParam);
  192.   inPt.y = HIWORD(lParam);
  193.   /* Convert to logical coordinates. */
  194.   DPtoLP(hDC, &inPt, 1);
  195.  
  196.   switch(iMessage)
  197.     {
  198.     case WM_RBUTTONDOWN:
  199.       /* Erase client area if not in middle of Bez. */
  200.       if (iState == NOT_STARTED)
  201.         InvalidateRect(hWnd, NULL, TRUE);
  202.       break;  /* WM_RBUTTONDOWN */
  203.  
  204.     case WM_LBUTTONDOWN:
  205.       switch(iState)
  206.         {
  207.         case NOT_STARTED:
  208.           iState = DRAG_HAND1;         /* starting drag */
  209.           hand1.x = ctrl1.x = inPt.x;  /* store user point 
  210.           hand1.y = ctrl1.y = inPt.y;     in statics */
  211.           break;  /* NOT_STARTED */
  212.         case WAIT_FOR_CTRL2:
  213.           iState = DRAG_HAND2;         /* starting drag */
  214.           hand2.x = ctrl2.x = inPt.x;  /* store user point 
  215.           hand2.y = ctrl2.y = inPt.y;     in statics */
  216.           SetROP2(hDC, R2_NOTXORPEN);  /* draw in XOR */
  217.           DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
  218.           break;  /* NOT_STARTED */
  219.         }  /* switch(iState) */
  220.       break;  /* WM_LBUTTONDOWN */
  221.  
  222.     case WM_MOUSEMOVE:
  223.       switch(iState)
  224.         {
  225.         case DRAG_HAND1:
  226.           SetROP2(hDC, R2_NOTXORPEN);     /* draw in XOR */
  227.           DrawHandle(hDC, ctrl1, hand1);  /* erase old */
  228.           hand1.x = inPt.x;               /* get new handle */
  229.           hand1.y = inPt.y;
  230.           DrawHandle(hDC, ctrl1, hand1);  /* draw new */
  231.           break;  /* DRAG_HAND1 */
  232.         case DRAG_HAND2:
  233.           SetROP2(hDC, R2_NOTXORPEN);     /* draw in XOR */
  234.           DrawHandle(hDC, ctrl2, hand2);  /* erase old */
  235.           DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
  236.           hand2.x = inPt.x;               /* get new handle */
  237.           hand2.y = inPt.y;
  238.           DrawHandle(hDC, ctrl2, hand2);  /* draw new */
  239.           DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
  240.           break;  /* DRAG_HAND1 */
  241.         }  /* switch(iState) */
  242.       break;  /* WM_MOUSEMOVE */
  243.  
  244.     case WM_LBUTTONUP:
  245.       switch(iState)
  246.         {
  247.         case DRAG_HAND1:
  248.           iState = WAIT_FOR_CTRL2;
  249.           SetROP2(hDC, R2_COPYPEN); /* COPY pen for final handle */
  250.           DrawHandle(hDC, ctrl1, hand1);    /* draw in COPY mode */
  251.           break;  /* DRAG_HAND1 */
  252.         case DRAG_HAND2:
  253.           iState = NOT_STARTED;
  254.           SetROP2(hDC, R2_COPYPEN); /* COPY pen for final handle */
  255.           DrawHandle(hDC, ctrl2, hand2);    /* draw in COPY mode */
  256.           DrawBez(hDC, ctrl1, hand1, hand2, ctrl2);
  257.           break;  /* DRAG_HAND2 */
  258.         }  /* switch(iState) */
  259.       break;  /* WM_LBUTTONUP */
  260.     }  /* switch(iMessage) */
  261.  
  262.   ReleaseDC(hWnd, hDC);
  263. }
  264.  
  265. BOOL FAR PASCAL AboutBez(HWND hDlg, unsigned iMessage,
  266.                          WORD wParam, LONG lParam)
  267. /*
  268. USE:  Application's "About" dialog box function.
  269. IN:   hDlg     : handle to dialog box
  270.       iMessage : message type
  271.       wParam   : auxiliary message info (act on IDOK, IDCANCEL)
  272.       lParam   : unused
  273. RET:  Return TRUE if processed appropriate message, FALSE otherwise.
  274. NOTE: Closes "About" box only when user clicks OK button
  275.       or system close. */
  276. {
  277.   switch (iMessage)
  278.     {
  279.     case WM_INITDIALOG:       /* initialize dialog box */
  280.       return (TRUE);
  281.     case WM_COMMAND:          /* received a command */
  282. /* IDOK if OK box selected; IDCANCEL if system menu close command */
  283.       if (wParam == IDOK || wParam == IDCANCEL)
  284.         {
  285.         EndDialog(hDlg, TRUE);  /* exit dialog box */
  286.         return(TRUE);           /* did proccess message */
  287.         }
  288.       break;  /* WM_COMMAND */
  289.     }  /* switch (iMessage) */
  290.   return (FALSE);               /* did not process message */
  291. }
  292.  
  293. void NEAR PASCAL DrawBez(HDC hDC, POINT ctrl1, POINT hand1,
  294.                          POINT hand2, POINT ctrl2)
  295. /*
  296. USE:  Draw Bezier curve given control and handle points.
  297. IN:   ctrl1,hand1,hand2,ctrl2 : control and handle points for Bezier
  298. NOTE: Set up, then call SubDivideBez(), the recursive de Casteljau
  299. routine, generate points along the Bez. Windows' Polyline() displays
  300. the Bez as a polygon. BEZ_DEPTH = recursive depth of de Casteljau.
  301. Initial POINT ctrl1 loaded here, then recursive routine calculates
  302. and loads the remaining 2^BEZ_DEPTH = (NUM_BEZPTS - 1) de Casteljau
  303. pts. */
  304. {
  305.   PtrBezPts = BezPts;        /* init ptr to start of array */
  306.   *PtrBezPts++ = ctrl1;      /* first control point special case */
  307.   SubDivideBez(ctrl1, hand1, hand2, ctrl2, BEZ_DEPTH); /* calc pts */
  308.   Polyline(hDC, BezPts, NUM_BEZPTS);  /* call Windows to draw */
  309. }
  310.  
  311. void NEAR PASCAL SubDivideBez(POINT p0, POINT p1,
  312.                               POINT p2, POINT p3, int depth)
  313. /*
  314.   USE:  Calculate de Casteljau construction points and break Bez
  315.         in two.
  316.   IN:   p0,p1,p2,p3 : control/handle/handle/control for Bez to
  317.         subdivide depth: current recursive depth of algorithm.
  318.   NOTE: Calculates the de Casteljau construction points so the
  319.   Bezier can be subdivided into 2 parts (left, then right) by
  320.   recursive calls to this routine. Recursion is broken off when
  321.   depth, decremented once for each recursion level, becomes 0.
  322.   This is the finest level of subdivision; the right-most point on
  323.   the small subdivided Bezier is also a point on the original
  324.   Bezier, so we load it into global array BezPts[] (thru PtrBezPts
  325.   which points into the array). */
  326. {
  327.   /* de Casteljau construction points: */
  328.   POINT q0, q1, q2, r0, r1, s0;
  329.  
  330.   /* depth == 0 means we are at the finest subdivision level:
  331.   grab point into global array and return, breaking off recursion. */
  332.   if (!depth)
  333.     {
  334.     *PtrBezPts++ = p3;
  335.     return;
  336.     }
  337.  
  338.   /* Calculate de Casteljau construction points as averages of
  339.      previous points (ie., midway points); note shift right is
  340.      fast division by 2. */
  341.   /* q's are midway between 4 incoming control and handle points. */
  342.   q0.x = (p0.x + p1.x) >> 1;  q0.y = (p0.y + p1.y) >> 1;
  343.   q1.x = (p1.x + p2.x) >> 1;  q1.y = (p1.y + p2.y) >> 1;
  344.   q2.x = (p2.x + p3.x) >> 1;  q2.y = (p2.y + p3.y) >> 1;
  345.  
  346.   /* r's are midway between 3 q's. */
  347.   r0.x = (q0.x + q1.x) >> 1;  r0.y = (q0.y + q1.y) >> 1;
  348.   r1.x = (q1.x + q2.x) >> 1;  r1.y = (q1.y + q2.y) >> 1;
  349.  
  350.   /* s0 is midway between 2 r's and is in middle of original Bez. */
  351.   s0.x = (r0.x + r1.x) >> 1;  s0.y = (r0.y + r1.y) >> 1;
  352.  
  353.   /* Decrement depth; subdivide incoming Bez into 2 parts:
  354.      left, then right.*/
  355.   SubDivideBez(p0, q0, r0, s0, --depth);
  356.   SubDivideBez(s0, r1, q2, p3,   depth);
  357. }
  358.  
  359. void NEAR PASCAL DrawHandle(HDC hDC, POINT p, POINT q)
  360. /*
  361.   USE:  Draws handle on screen from p to q with hRedPen.
  362.   IN:   hDC : handle to display context
  363.         p,q : handle start and end points
  364.   NOTE: Don't CreatePen or delete--these are done globally once only.
  365.   Handles are drawn with little 3x3 pixel boxes at each end.
  366.   Each pixel is LogPerDevice logical units; logical units must be
  367.   used for the boxes since we are in MM_ISOTROPIC mapping mode.
  368. */
  369. {
  370.   HPEN origPen;    /* DC's orinal pen */
  371.   int  xLeft;      /* left coord of little box at end of handle */
  372.   int  xRight;     /* right coord of little box */
  373.   int  y;          /* y coord of little box */
  374.  
  375.   /* Save original pen, select red pen. */
  376.   origPen = SelectObject(hDC, hRedPen);
  377.  
  378.   /* Draw handle. */
  379.   MoveTo(hDC, p.x, p.y);  LineTo(hDC, q.x, q.y);
  380.  
  381.   /* Set left and right coords around q.x (3 pixels). Remember
  382.      Windows lines do not draw last pixel. */
  383.   xLeft  = q.x - LogPerDevice;
  384.   xRight = q.x + (LogPerDevice << 1);
  385.  
  386.   /* Init y coord 1 pixel below q.y. */
  387.   y = q.y - LogPerDevice;
  388.  
  389.   /* Draw little box : 3x3 pixels. */
  390.   MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y);  y += LogPerDevice;
  391.   MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y);  y += LogPerDevice;
  392.   MoveTo(hDC, xLeft, y); LineTo(hDC, xRight, y);  y += LogPerDevice;
  393.  
  394.   /* Re-select original pen. */
  395.   SelectObject(hDC, origPen);
  396. }
  397.