Three-Dimensional Modeling Under Windows 3.1



March 01, 1993
URL:http://drdobbs.com/three-dimensional-modeling-under-windows/184402682

March 1993/Three-Dimensional Modeling Under Windows 3.1/Figure 1

Figure 1 The Viewing Transformation

March 1993/Three-Dimensional Modeling Under Windows 3.1/Figure 2

Figure 2 Wire-Frame and Solid Models

March 1993/Three-Dimensional Modeling Under Windows 3.1/Listing 1

Listing 1 3d.c — creates a General Dynamics F16 Falcon aircraft

#include "windows.h"
#include <math.h>
#include <stdlib.h>

//******************************************************************
//Title:         3D.C
//Author:        Thomas W. Olsen
//Version:       1.0
//Compiler:      Microsoft C/C++ 7.0
//                 rc /r 3d.  rc
//                 cl /c /AL /Gsw /W3 /Oas /Zpe /Zi /FPi 3d.c
//                 link /CO /NOD 3d,,, libw llibcew win87em, 3d.def
//                 rc 3d.exe
//*****************************************************************
//********************** Various Constants *************************
#define CENTER                 0
#define PI                      3.141593
#define RADIANS(a)            (a * (PI / 180.0))
#define DEGREES(a)            (a * (180.0 / PI))
#define NO_OF_VERTICES       66
#define NO_OF_SURFACES       45
#define NO_OF_SURFACE_VERTICES 220
#define VIEW_RATIO        (40 / 10)
#define MIN_DEGREES            0
#define MAX_DEGREES          359
#define ROTATE_DEGREES         5
//*********************  Structure Definitions  **********************
typedef struct tagPOINT3D
{
    double x;
    double y;
    double z;
} POINT3D;

typedef struct tagVERTEX
{
    POINT3D world;               // World Coordinates
    POINT3D eye;                 // Eye Coordinates
    POINT   screen;              // Screen Coordinates
} VERTEX;

typedef struct tagSURFACE
{
    int mapIndex;               // Index Into the Surface Map Array
    int noOfVertices;          // No of Vertices Forming the Surface
    int depthIndex;             // Used in Determining Depth of Surface
    int brushColor;
} SURFACE;

typedef struct tagOBJECT
{
    VERTEX  vertex [NO_OF_VERTICES];
    SURFACE info[NO._OF_SURFACES];
    int     map[NO_OF_SURFACE_VERTICES];
} OBJECT;
//************************* Static Data ***************************
OBJECT f16 =
{ 
    {    // Vertex Array
        {  0.00, 0.00, 0.00 },                                //Center
        { 0.00, -17.00, 9.0 }, { 0.00, -13.00, 9.00 },      //Tail
        { 0.00, -14.50, 3.00 }, { 0.00, -8.00, 3.00 },
        { 0.00, -14.50, 2.00 }, { 0.00, -5.00, 2.00 },
        { -0.50, -16.00, 1.00 }, { -1.00, -16.00, 0.00 },   //Exhaust
        { -0.50, -16.00, -0.50 }, { 0.00, -16.00, -1.00 },
        { 0.50, -16.00, -0.50 }, { 1.00, -16.00, 0.00},
        { 0.50, -16.00, 1.00 },
        { -1.00, -13.00, 2.00 }, { -2.00, -13.00, 0.00 },   //Fuse
        { -1.50, -13.00, -1.50 }, { 0.00, -13.00, -2.00 },
        { 1.50, -13.00, -1.50 }, { 2.00, -13.00, 0.00 },
        { 1.00, -13.00, 2.00 }, { -1.00, 7.00, 2.00 },
        { -2.00, 7.00, 0.00 }, { -1.50, 7.00, -1.50 },
        { 0.00, 7.00, -2.00 }, { 1.50, 7.00, -1.50 },
        { 2.00, 7.00, 0.00 }, { 1.00, 7.00, 2.00 },
        { 2.00, -8.00, 0.00 }, { 11.00, -8.00, 0.00 },      //Wings
        { 11.00,-5.00,0.00 }, { 5.00, 0.00, 0.00 },
        { 2.00, 7.00, 0.00 }, { -2.00, -8.00, 0.00 },
        { -11.00, -8.00, 0.00 }, { -11.00, -5.00, 0.00 },
        { -5.00, 0.00, 0.00 }, { -2.00, 7.00, 0.00 },
        { 0.50, 2.00, 2.00 }, { -0.50, 2.00, 2.00 },        //Cockpit
        { 1.00, 5.00, 2.00 }, { 0.50, 5.00, 3.50 },
        { -0.50, 5.00, 3.50 }, { -1.00, 5.00, 2.00 },
        { 1.00, 8.00, 2.00 }, { 0.50, 8.00, 3.50 },
        { -0.50, 8.00, 3.50 }, { -1.00, 8.00, 2.00 ),
        { 0.50, 11.00, 2.00 }, { -0.50, 11.00, 2.00 },
        { 0.00, 7.00, -1.00 }, { -1.00, 11.00, 2.00 },      //Subfuse
        { -2.00, 11.00, 0.00 }, { 0.00, 11.00, -1.00 },
        { 2.00, 11.00, 0.00 }, { 1.00, 11.00, 2.00 },
        { 0.00, 11.00, -1.00 }, { 0.00, 17.00, 0.00 },      //Nose
        { -2.00, -16.00, 0.00 }, { -8.00, -16.00, 0.00 },  //Elevators
        { -8.00, -14.00, 0.00 }, { -2.00, -10.00, 0.00 },
        { 2.00, -16.00, 0.00 }, { 8.00, -16.00, 0.00 },
        { 8.00, -14.00, 0.00 }, { 2.00, -10.00, 0.00 }
    },
    {    // Surface Info ... (Points to Surface Map)
        { 0,  5, 60,  DKGRAY_BRUSH },  { 5,  5, 64, DKGRAY_BRUSH }, //Elevators
        { 10, 5, 60,  DKGRAY_BRUSH },  { 15, 5, 64, DKGRAY_BRUSH },
        { 20, 5,  1,  GRAY_BRUSH },    { 25, 5,  4, LTGRAY_BRUSH },    //Tail
        { 30, 5,  1,  GRAY_BRUSH },    { 35, 5,  4, LTGRAY_BRUSH },
        { 40, 5, 14,  BLACK_BRUSH },  ( 45, 5, 15,  DKGRAY_BRUSH },    //Exhaust
        { 50, 5, 16,  BLACK_BRUSH },  ( 55, 5, 17,  BLACK_BRUSH },
        { 60, 5, 18,  DKGRAY_BRUSH }, { 65, 5, 19,  BLACK_BRUSH },
        { 70, 5, 20,  DKGRAY_BRUSH },
        { 75, 5, 22,  DKGRAY_BRUSH }, { 80, 5,  23, GRAY_BRUSH },   //Fuse
        { 85, 5, 24,  DKGRAY_BRUSH }, { 90, 5,  25, DKGRAY_BRUSH },
        { 95, 5, 26,  GRAY_BRUSH },   { 100, 5, 27, DKGRAY_BRUSH },
        { 105, 5, 20, GRAY_BRUSH, },
        { 110, 6, 30, DKGRAY_BRUSH }, ( 116, 6, 35, DKGRAY_BRUSH }, //Wings
        { 122, 6, 30, DKGRAY_BRUSH }, { 128, 6, 35, DKGRAY_BRUSH },
        { 134, 5, 21, LTGRAY_BRUSH }, { 139, 5, 22, LTGRAY_BRUSH }, //Subfuse
        { 144, 5, 50, LTGRAY_BRUSH }, { 149, 5, 26, LTGRAY_BRUSH },
        { 154, 5, 27, LTGRAY_BRUSH },
        { 159, 4, 55, GRAY_BRUSH },   { 163, 4, 54, GRAY_BRUSH },   //Nose
        { 167, 4, 56, GRAY_BRUSH },   { 171, 4, 52, GRAY_BRUSH },
        { 175, 4, 51, LTGRAY_BRUSH },
        { 179, 5, 41, WHITE_BRUSH },  { 184, 5, 45, WHITE_BRUSH },  //Cockpit
        { 189, 5, 46, WHITE_BRUSH },  { 194, 4, 41, WHITE_BRUSH },
        { 198, 4, 42, WHITE_BRUSH },  { 202, 5, 41, WHITE_BRUSH },
        { 207, 5, 46, WHITE_BRUSH },  { 212, 4, 45, WHITE_BRUSH },
        { 216, 4, 46, WHITE_BRUSH }
    },
    {    // Surface Map ... (Points to Vertex Array)
        58, 61, 60, 59, 58, 62, 63, 64, 65, 62, 58, 59, 60, 61, 58, // Elevators
        62, 65, 64, 63, 62,
         1,  3,  4,  2,  1, 3,  5,  6,  4,  3,  1,  2,  4,  3,  1, // Tail
         3,  4,  6,  5,  3,
        14, 15,  8,  7, 14, 15, 16,  9,  8, 15, 16, 17, 10,  9, 16, // Exhaust
        17, 18, 11, 10, 17, 18, 19, 12, 11, 18, 19, 20, 13, 12, 19,
        20, 14, 7, 13, 20,
        14, 21, 22, 15, 14, 15, 22, 23, 16, 15, 16, 23, 24, 17, 16, // Fuse
        17, 24, 25, 18, 17, 18, 25, 26, 19, 18, 19, 26, 27, 20, 19,
        20, 27, 21, 14, 20,
        28, 29, 30, 31, 32, 28, 33, 37, 36, 35, 34, 33, 28, 32, 31, // Wings
        30, 29, 28, 33, 34, 35, 36, 37, 33,
        21, 51, 52, 22, 21, 22, 52, 53, 50, 22, 50, 53, 54, 26, 50, // Subfuse
        26, 54, 55, 27, 26, 27, 55, 51, 21, 27,
        55, 54, 57, 55, 54, 56, 57, 54, 56, 52, 57, 56, 52, 51, 57, 52, // Nose
        51, 55, 57, 51,
        41, 42, 39, 38, 41, 45, 46, 42, 41, 45, 48, 49, 46, 45, 48, // Cockpit
        40, 41, 38, 40, 42, 43, 39, 42, 44, 45, 41, 40, 44,
        46, 47, 43, 42, 46, 48, 45, 44, 48, 49, 47, 46, 49
    }
};

BOOL wireFrame = FALSE;
double distance = 75, thetaDegrees = 90, phiDegrees = 60;
//************************** Static Data ***************************
LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void DrawObject(HWND hWnd, HDC hDC, OBJECT *object );
int __cdecl compareProc(const void *elem1, const void *elem2 );
//************************** Program Begin *************************
int PASCAL WinMain(HANDLE hInst, HANDLE hPrevInst, LPSTR lpCmdLine, int numCmdShow )
{
    MSG       msg;
    HWND      hWnd;
    WNDCLASS  wc;
    //************************ Setup Window ************************
    wc.style          = (UINT) NULL;
    wc.lpfnWndProc   = WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance      = hInst;
    wc.hIcon           = LoadIcon( NULL, IDI_APPLICATION);
    wc.hCursor        = LoadCursor( NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName  = (LPSTR) "Menu";
    wc.lpszClassName = (LPSTR) "3DClass";

    if (!RegisterClass(&wc))
        return(FALSE);

    hWnd = CreateWindow( "3DClass", "3D Modeling Example",
                         WS_OVERLAPPEDWINDOW | WS_SCROLL | WS_HSCROLL,
                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, NULL, NULL, hInst, NULL );
    if (!hWnd)
        return (FALSE);

    SetScrollRange( hWnd, SB_HORZ, MIN_DEGREES, MAX_DEGREES, TRUE );
    SetScrollRange( hWnd, SB_VERT, MIN_DEGREES, MAX_DEGREES, TRUE );
    SetScrollPos( hWnd, SB_HORZ, (int) thetaDegrees, TRUE );
    SetScrollPos( hWnd, SB_VERT, (int) phiDegrees, TRUE );

    ShowWindow( hWnd, numCmdShow );

    while (GetMessage(&msg, NULL, NULL, NULL)) /* Typical Mossage Loop */
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    return (msg.wParam);
}

LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT paint;
    HDC hDC;
    HMENU hMenu;
    int vPos, hPos;

    switch (message)
    {
        case WM_COMMAND:
            hMenu = GetMenu( hWnd );
            CheckMenuItem( hMenu, wParam, MF_CHECKED);

            if (wParam == 1)
            {
                CheckMenuItem( hMenu, 2, MF_UNCHECKED);
                wireFrame = TRUE;
            }
            else
            {
                CheckMenuItem( hMenu, 1, MF_UNCHECKED);
                wireFrame = FALSE;
            }
            InvalidateRect( hWnd, NULL, TRUE );
            break;

        case WM_DESTROY:
            PostQuitMessage( NULL );
            break;

        case WM_HSCROLL:
            if (wParam == SB_THUMBTRACK)
                break;

            hPos = GetScrollPos( hWnd, SB_HORZ);

            switch( wParam )
            {
                case SB_TOP:
                    hPos = MIN_DEGREES;
                    break;
                case SB_BOTTOM:
                    hPos = MAX_DEGREES;
                    break;
                case SB_LINEUP:
                case SB_PAGEUP:
                    hPos -= ROTATE_DEGREES;
                    break;
                case SB_PAGEDOWN:
                case SB_LINEDOWN:
                    hPos += ROTATE_DEGREES;
                    break;
                case SB_THUMBPOSITION:
                    hPos = LOWORD(lParam);
                    break;
            }

            if (hPos < MIN_DEGREES)
                hPos = MAX_DEGREES;
            else
            if (hPos > MAX_DEGREES)
                hPos = MIN_DEGREES;

            SetScrollPos( hWnd, SB_HORZ, hPos, TRUE );
            thetaDegrees = (double) hPos;
            InvalidateRect( hWnd, NULL, TRUE );
            break;

        case WM_VSCROLL:
            if (wParam == SB_THUMBTRACK)
                break;

            vPos = GetScrollPos( hWnd, SB_VERT );

            switch( wParam )
            {
                case SB_TOP:
                    vPos = MIN_DEGREES;
                    break;
                case SB_BOTTOM:
                    vPos = MAX_DEGREES;
                    break;
                case SB_LINEUP:
                case SB-PAGEUP:
                    vPos -= ROTATE_DEGREES;
                    break;
                case SB_PAGEDOWN:
                case SB_LINEDOWN:
                    vPos += ROTATE_DEGREES;
                    break;
                case SB_THUMBPOSITION:
                    vPos = LOWORD(lParam);
                    break;
            }

            if (vPos < MIN_DEGREES)
                vPos = MAX_DEGREES;
            else
            if (vPos > MAX_DEGREES)
                vPos = MIN_DEGREES;

            SetScrollPos( hWnd, SB_VERT, vPos, TRUE );
            phiDegrees = (double) vPos;
            InvalidateRect( hWnd, NULL, TRUE );
            break;

        case WM_SIZE:
            InvalidateRect( hWnd, NULL, TRUE );
            break;

        case WM_PAINT:
            hDC = BeginPaint( hWnd, &paint );
            DrawObject(hWnd, hDC, &f16);
            ReleaseDC( hWnd, hDC );
            EndPaint( hWnd, &paint );
            break;

        default:
            return (DefWindowProc(hWnd, message, wParam, lParam));
    }
}

void DrawObject(HWND hWnd, HDC hDC, OBJECT *object)
{
    double  sinTheta, cosTheta, sinPhi, cosPhi;
    double  s1, s2, s3;
    POINT3D *v1, *v2, *v3;
    POINT   center;
    RECT    rect;
    POINT   points[10];
    HBITMAP hBitmap, hOldBitmap;
    HRGN    hRgn;
    HDC     hMemDC;
    int     surface, vertex, mapIndex, vertexIndex, loop;

    GetClientRect( hWnd, &rect);       // Determine Size of Client Area
    center.x = (rect.right / 2);       // Calculate X-Centerpoint
    center.y = (rect.bottom / 2);      // Calculate Y-Centerpoint

    hRgn          = CreateRectRgn( rect.left, rect.top, rect.right, rect.bottom );
    hMemDC       = CreateCompatibleDC( hDC );
    hBitmap     = CreateCompatibleBitmap( hDC, rect.right, rect.bottom);
    hOldBitmap = SelectObject( hMemDC, hBitmap );
    //**********************************************************************
    //*               Precalculate SIN(x) and COS(x)                         *
    //**********************************************************************
    cosTheta = cos( RADIANS(thetaDegrees) );
    sinTheta = sin( RADIANS(thetaDegrees) );
    cosPhi    = cos( RADIANS(phiDegrees) );
    sinPhi    = sin( RADIANS(phiDegrees) );
    //******************************************************************
    //*                 Calculate Eye and Screen Coordinates               *
    //******************************************************************
    for (loop = 1; loop < NO_OF_VERTICES; loop++)
    {
        object->vertex[loop].eye.x =
                            (-object->vertex[loop].world.x * sinTheta) +
                            (object->vertex[loop].world.y * cosTheta);
        object->vertex[loop].eye.y =
                            (-object->vertex[loop].world.x * cosTheta * cosPhi) -
                            (object->vertex[loop].world.y * sinTheta * cosPhi) +
                            (object->vertex[loop].world.z * sinPhi);
        object->vertex[loop].eye.z =
                            (-object->vertex[loop].world.x * sinPhi * cosTheta) -
                            (object->vertex[loop].world.y * sinTheta * sinPhi) -
                            (object->vertex[loop].world.z * cosPhi) + distance;

        object->vertex[loop].screen.x = (int)
            (VIEW_RATIO * (object->vertex[loop].eye.x / object->vertex[loop].eye.z) * center.y + center.x);
        object->vertex[loop].screen.y = (int)
            (-VIEW_RATIO * (object->vertex[loop].eye.y / object->vertex[loop].eye.z) * center.y + center.y);
    }
    //******************************************************************
    //*                           Draw Object                                 *
    //******************************************************************
    FillRgn( hMemDC, hRgn, GetStockObject(BLACK_BRUSH));
    SelectObject( hMemDC, GetStockObject(wireFrame == TRUE ? WHITE_PEN:BLACK_PEN) );

    if (wireFrame == FALSE)
        qsort( object->info, NO_OF_SURFACES, sizeof(SURFACE), compareProc );

    for (surface = 0; surface < NO_OF_SURFACES; surface++)
    {
        mapIndex = object->info[surface].mapIndex;

        if (wireFrame == FALSE) // No Hidden Surface Removal For Wire Frame
        {
            v1 = &object->vertex[ object->map[ mapIndex ] ].eye; // Setup pointers to three
            v2 = &object->vertex[ object->map[ mapIndex+1 ] ].eye; // surface vertices
            v3 = &object->vertex[ object->map[ mapIndex+2 ] ].eye;

            s1 = v1->x * (v2->y * v3->z - v3->y * v2->z); s1 = (-1) * s1;
            s2 = v2->x * (v3->y * v1->z - v1->y * v3->z); // Perform dot product on surface
            s3 = v3->x * (v1->y * v2->z - v2->y * v1->z); // vectors to find normal vector
        }

        if (wireFrame == TRUE || s1 - s2 - s3 <= 0.0)
        {
            for (vertex = 0; vertex < object->info[surface].noOfVertices; vertex++, mapIndex++)
            {
                vertexIndex      = object->map[mapIndex];
                points[vertex].x = object->vertex[vertexIndex].screen.x;
                points[vertex].y = object->vertex[vertexIndex].screen.y;
            }

            if (wireFrame == TRUE)
                Polyline( hMemDC, &points[0], vertex;
            else
            {
                SelectObject( hMemDC, GetStockObject(object->info[surface].brushColor) );
                Polygon( hMemDC, &points[0], vertex);
            }
        }
    }
    BitBlt( hDC, 0, 0, rect.right, rect.bottom, hMemDC, 0, 0, SRCCOPY);

    SelectObject( hMemDC, hOldBitmap );
    DeleteObject( hBitmap );
    DeleteObject( hRgn );
    DeleteDC( hMemDC );
}

int _cdecl compareProc( const void *elem1, const void *elem2 )
{
    if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z >
        f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
        return -1;
    if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z <
        f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
        return 1;
    else
        return 0;
}
/* End of File*/
March 1993/Three-Dimensional Modeling Under Windows 3.1/Listing 2

Listing 2 3d.def

NAME          3D

DESCRIPTION   '3D Modeling Example'

EXETYPE       WINDOWS
STUB          'WINSTUB.EXE'

CODE          PRELOAD MOVEABLE
DATA         PRELOAD MOVEABLE MULTIPLE

HEAPSIZE      2048
STACKSIZE     4096

EXPORTS
            WindowProc     @1
March 1993/Three-Dimensional Modeling Under Windows 3.1/Listing 3

Listing 3 3d.rc

#include "windows.h"

Menu MENU
{
    POPUP "&Draw"
    {
       MENUITEM "&Wire Frame", 1
       MENUITEM "&Solid",      2, CHECKED
    }
}

March 1993/Three-Dimensional Modeling Under Windows 3.1

Three-Dimensional Modeling Under Windows 3.1

Thomas W. Olsen


Thomas writes a variety of Windows and DOS software for a major insurance company. He can be reached on the CompuServe Information Service at (76450,1767).

Nothing communicates an idea better than a picture. It's the defining principle behind the popularity of Microsoft Windows and other graphical environments. Not long ago, though, you might recall serious debate over whether Windows would ever meet the performance demands of a graphical user interface. Much of the trepidation resulted, no doubt, from the tremendous freedom of MS-DOS programs to directly access video hardware. However, faster CPUs, accelerated video adapters, local bus connections, multimedia, and a mature Graphical Display Interface (GDI) have since conspired in favor of Windows.

These changes pose significant opportunities in 3-D modeling software development. Three-dimensional modeling has become increasingly important over time because it mimics elements in the real world. This article focuses on simple strategies for 3-D modeling. The accompanying source code was compiled and linked with the Microsoft C/C++ Optimizing Compiler Version 7.0 and Microsoft Windows 3.1 Software Developer Kit (SDK). Every effort has been made to ensure compatibility with other compilers. Compile instructions are provided in comments at the top of each listing. The bitmaps were created with Microsoft Paintbrush.

This article barely scratches the surface of 3-D graphics. Rendering, shading, ray tracing, and texture mapping get a more thorough treatment from the references in the bibliography. This article will present a building block for such advanced features.

Do not confuse 3-D modeling with Windows metafiles. A metafile is a binary-encoded collection of GDI function calls. You create one by sending the output from various GDI function calls to a special device context. A recorded metafile can be played back by passing its associated resource handle to PlayMetaFile. The resulting picture may look three-dimensional but it's just a two-dimensional facade.

Vector Graphics

Microsoft Windows derives its characteristic look and feel primarily from raster-based or bitmapped graphics technology. Most fonts, controls, and icons are nothing more than bitmaps — two-dimensional blocks of pixels — that appear three-dimensional due to varying color gradients. Bitmaps look great but generally experience some kind of image distortion when rotated, stretched, or scaled onto devices of varying resolutions. To address such deficiencies Microsoft incorporated so-called TrueType font technology into Windows 3.1. TrueType is a form of vector-based graphics, in which images are constructed with individual graphical primitives such as lines, points, rectangles, ellipses, and curves rather than bitmaps. Vector graphics are scalable, require less memory than bitmaps, and enable us to model three-dimensional objects with relative ease.

Coordinate Systems

The Windows GDI does not contain explicit support for 3-D modeling, but it is not difficult to build a suitable framework atop existing primitive functions. Nearly any 3-D object can be drawn with a set of interconnected points or vertices. For example, a simple cube has eight vertices (one for each corner) while the General Dynamics F16 Falcon aircraft in 3D.C in Listing 1 (along with the files in Listing 2 and Listing 3) contains literally hundreds of vertices. Of course, a collection of points is worthless without some frame of reference, so they are placed in a domain called the World Coordinate System (WCS). You can change the object's orientation in WCS by multiplying each vertex by a series of transformation matrices. There are distinct transformation matrices for rotation, reflection, shearing, scaling, and translation operations, respectively.

Try to imagine yourself floating in space around a motionless object. As you change position, each WCS vertex is transformed with respect to an Eye Coordinate System (ECS) that emanates from your eye and points toward the object (Figure 1) . To complicate matters even more, the resulting ECS vertices must be transformed from three-dimensional to two-dimensional screen coordinates (SCS) before the object can be displayed. Once these screen positions are known, you can connect-the-dots to produce a transparent wireframe model, or use filled polygons for a more realistic solid model (Figure 2) . Three-dimensional objects are drawn one surface at a time, so vertices are generally grouped in that order.

Hidden-Surface Removal

Hidden-surface removal is one of the more complicated and computation-in-tensive facets of 3-D modeling. The most popular method of hidden-surface removal is called backplane elimination. Basically, it involves computing whether a normal (perpendicular) vector emanating from a given surface points away from or toward the viewer's line of sight. Those surfaces facing away from the viewer cannot be seen and, therefore, need not be drawn. It does have its drawbacks, too. Objects with irregular or overlapping surfaces will not be drawn properly. One quick-and-dirty solution is to sort the surfaces in terms of decreasing distance from the viewer (ECS z-coordinate). Surfaces lying farther away from the viewer are drawn first and subsequently masked by closer surfaces. Depth sorting has its flaws but performance-conscious applications usually don't mind.

Constructing Models

3D.C (Listing 1) supports both wireframe and solid models. It first creates a window with horizontal and vertical scroll bars, and uses SetScrollRange to lock the thumb between 0 and 360 degrees. Depressing the scroll bars changes the angular position of the viewer relative to the object. This is especially convenient because the viewer's position is given in spherical coordinates (distance, theta, phi). All measurements are given in device units. The F16 object "database" has been optimized to keep code size to a minimum.

3D.C calls DrawObject whenever the viewer depresses the scroll bars or resizes the window. DrawObject determines the center point of the window and creates a compatible work bitmap. Using an intermediate bitmap prevents the flashing effects that occur while drawing straight to a display context. DrawObject also precalculates sine and cosine values for global variables theta and phi. These values are needed when f16.vertex[].world vertices are transformed to f16.vertex[].eye vertices and, finally, to f16.vertex[].screen coordinates. DrawObject clears the work bitmap and loops through each surface in the f16.info[] array. For solid models, DrawObject performs a depth sort on the f16.vertex[].eye vertices, removes hidden surfaces with backplane elimination, selects a brush color from f16.info[].brushColor, and calls Polygon; otherwise, it calls PolyLine for a wire-frame surface. DrawObject then updates the client area with the completed work bitmap and deletes unused resources.

Computations

Floating-point computations incur a considerable amount of overhead — even with a math coprocessor installed — but you can improve performance significantly by substituting fixed-point integers for floating-point numbers. Fixed-point integers incorporate both the whole and fractional components of floating-point numbers but can be manipulated in single arithmetic operations, such as IMUL and IDIV. For example, it is possible to represent the floating-point number 12.345 with integer 12345 by shifting the decimal place three positions. There are certain problems with this technique, as well. Integers can only represent so many digits before overflowing, so you must strike a balance between the scale and precision of the 3-D model.

NASA's Jet Propulsion Laboratories unveiled a computer-generated film several months ago that depicted the surface topography of some distant planet. JPL had downloaded countless bits of radar imaging data from a distant probe into three Cray super computers and rendered the surreal landscape frame-by-frame over the course of three solid weeks. The resulting bitmaps were finally transferred to videotape and made available for public consumption. There's a lesson hiding behind this madness. Even though real-time 3-D graphics were out of the question, JPL eventually got what it paid for by blending vector and raster technologies.

References

Adams, Lee. 1986. High-Performance CAD Graphics in C. Blue Ridge Summit, PA: Windcrest/Tab.

Microsoft Corp. 1991. Microsoft Windows Multimedia Authoring and Tools Guide. Redmond, WA: Microsoft Press.

Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Reference. Redmond, WA: Microsoft Press.

Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Workbook. Redmond, WA: Microsoft Press.

Park, Chan S. 1985. Interactive Microcomputer Graphics. Reading, MA: Addison-Wesley Publishing Company.

Petzold, Charles. 1990. Programming Windows. Redmond, WA: Microsoft Press.

Wilton, Richard. 1987. Programmer's Guide to PC & PS/2 Video Systems. Redmond, WA: Microsoft Press.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.