3-D Texture Mapping

Texture mapping allows you to project a 2-D image, or texture map, onto a flat polygon that has been placed on a 3-D surface. Jeremy's program draws a rotating cube (the model) painted with three different texture maps.


July 01, 1994
URL:http://www.drdobbs.com/parallel/3-d-texture-mapping/184409270

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

Figure 4


Copyright © 1994, Dr. Dobb's Journal

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

Figure 4


Copyright © 1994, Dr. Dobb's Journal

JUL94: 3-D Texture Mapping

3-D Texture Mapping

Mapping 2-D images onto a 3-D surface

Jeremy Spiller

Jeremy graduated from Fitchburg State College in 1993 and works for SKY-SKAN Inc. (Nashua, NH). He can be contacted at P.O. Box 1094, Townsend, MA 01469.


Texture mapping allows you to project a two-dimensional image, or texture map, onto a flat polygon placed on a three-dimensional surface. Drawing 3-D graphics involves two steps: constructing and manipulating a model, then projecting that model onto a 2-D flat screen. The program presented in this article draws a rotating cube (the model) with a different picture painted on each visible side. The pictures I've chosen are simple mathematical formulas; however, you can place any picture imaginable on the cube--even play a movie on each of the six sides as it rotates!

Manipulating a Model

Constructing a model can be a complex task. For the moment, assume that the model has already been constructed out of an array of points in 3-D space. While there are many ways to transform the model, the three basic operations discussed here are translating (or moving), scaling, and rotating. Matrix multiplication makes these transformations possible. Multiplying the appropriate matrix by each of the points in the model rotates it by 30 degrees around the z-axis. Multiplying with a different matrix moves the model to a new location. A matrix that contains information to translate, scale, or rotate a model is referred to as a "transformation matrix."

Scaling the model moves all the points inwards or outwards from the origin, 0,0,0 (where the viewer is located). Rotation can be broken down into three separate operations called "pitch" (rotation around the x-axis), "yaw" (rotation around the y-axis), and "roll" (rotation around the z-axis). With scaling and rotation, it's important to have the proper origin; otherwise, the center of the model will also be moved to a new location (see Figure 1).

Listing One includes the implementation of the texture-mapping algorithm. The functions Translate, Scale, RotateX, RotateY, and RotateZ can be used individually or in any combination to create matrices that perform these operations upon a model; see Figures 2, 3, and 4. For instance, if you wanted to move the center of a model to the origin, rotate it by 30 degrees around the x-axis, rescale the model to twice its size, and move it 100 units back from the viewer, you would perform the operation in Figure 5(a) on each of the points in the model. Notice that the effects of the operations are from right to left and cannot be reversed without reversing the effect. This is because matrix multiplication, unlike real multiplication, is not commutative--A*B does not equal B*A. On the other hand, matrix multiplication is associative--A*(B*C) equals (A*B)*C. Therefore, you can assign a single transformation matrix with the combined effect; see Figure 5(b). Whenever you want to perform all of these operations on the model, you need only multiply the transformation matrix by each point NewPoint=T*OldPoint.

The main() function in Listing One constructs a cube (actually, a 3-D rectangle) and demonstrates how to construct a simple model. The six sides of the cube are constructed and stored in the variables S1 through S6. Once in the proper spot, they need never be recalculated. This saves seven matrix multiplications, not to mention the sine and cosine calculations, each time the cube is rotated. Once it has been constructed, the model is manipulated with the transformation matrix Cube. The cube is translated to its center, rotated about the origin (which is now at the cube's center), then translated away from the viewer.

Projecting a Model

In simple terms, as a model moves away from the viewing screen, it gets smaller. To project a point onto the screen, the x- and y-coordinates are divided by the z-coordinate. After the point has been projected onto an imaginary viewing screen, it must be scaled and translated for a real monitor. This is done in main() with Cube=ScaleXYZ (230,200,1)*Cube, which scales for the unit pixel. The x- and y-coordinates are scaled differently because the pixels are about 15 percent taller (in this video mode) than they are wide. The actual values in this example are somewhat arbitrary. (The object can be moved back and forth on the z-axis to make it appear larger and smaller.) However, values that are too large will make the model appear flat and not three dimensional, while values that are too small will make the object appear distorted. After the points have been projected, they are translated (through the global variables MX and MY) onto the center of the screen.

The Find_Outline function calculates where the four corners of the rectangle will land on the screen when transformed through the matrix M. The Tran function maps each of them through the matrix M into its new x-, y-, and z-coordinates. They are then projected and translated onto the screen. The do_line function clips and traces the outline of the rectangle into the arrays MinX and MaxX, so that the area inside the rectangle can be painted.

Texture Mapping

Often a model consists of a few points connected by lines. In these cases, the points are projected onto the screen, and the computer simply connects the dots. If the model consists of opaque polygons, the computer connects the dots to form a polygon, which is then painted a solid color. Texture mapping, however, allows you to place an image in each of the polygons. The image (or texture map) must be projected onto the 2-D screen in the proper perspective to look like it is a flat surface in 3-D space that has been translated, scaled, and rotated into the polygon.

One approach is to treat the 2-D texture map as a model and translate, scale, and rotate it into the proper place. It would then be a simple matter to project each point from the texture map onto the screen. While this works, it's inefficient, and the results can possibly be quite ugly. Consider that squeezing a 100x100 texture map into a 10x10 square on the screen means drawing 10,000 pixels when only 100 pixels are required. The solution is to map the points from the screen onto the texture map. This is what Project_Plane does. To accomplish this, apply the inverse of the projection formula to each point on the screen. Any point on a 2-D texture map can be projected onto the screen using the formula in Figure 6. The numerator of the equations is the x- and y-coordinates of the point in 3-D space, while the denominator is the z-coordinate. Likewise, points can be projected from the screen onto the texture map with the same equations, but with different constants.

After finding the outline, Project_Plane calculates the constants necessary to map points from the screen onto the texture map. Fixed-point variables are used to speed up calculations inside the loops. The value I've chosen for FIXED_POINT scales the constants to be as large as possible without overflowing. If you use a larger texture map (or a higher-resolution screen), you may need to use a smaller constant. The for loop scans down the outline map looking for places on the screen to paint. Once located, the constants previously derived are used to calculate the inverse x-, y-, and z-coordinates. The z-coordinate is forced to stay odd to prevent an accidental divide by zero. The final texture-map coordinates are calculated and stored in GridX and GridY.

The innermost while loop scans across the screen until the end of the area to be filled is found. Two optimizations are performed inside this loop. The first takes advantage of the fact that y is a constant. Therefore, x=(Ax+By+C) can be rewritten as x=(Ax+j), where the constant j=By+C. Similar optimizations are performed for y and z, saving a total of three multiplications per pixel. The other optimization takes advantage of the fact that the x screen coordinate is always incremented by 1 on each iteration of the while loop. The difference in x is given by the equation [delta]x=(A(x+1)+j)--(Ax+j)=A. Therefore, finding the next x-, y-, and z-coordinates is a simple matter of adding dx, dy, and dz. This eliminates the other three multiplies, leaving only two divides and three adds per pixel.

Visibility Test

Even though a cube has six sides, no more than three are visible at any one time. This means that some sides of the cube must cover other sides. The simplest method of removing the unseen sides (that is, performing hidden-surface removal) is to remove all sides facing away from the viewer. The plane-equation method can be used to determine which side of a plane the viewer is on. Given the coefficients A, B, C, and D of a plane and any point (x,y,z), the formula Ax+By+Cz+D will be 0 if the point is on the plane, positive if the point is in front of the plane, and negative if the point is behind the plane. The coefficients of the plane can be derived from any three points on the plane by using the equations shown in Figure 7. Since you are interested only in whether or not a plane is visible from the origin, the x-, y-, and z-coordinates will be 0, and you only need to find the single coefficient D. The easiest three points to use to calculate the coefficient D are (0,0,0), (0,1,0), and (1,0,0), which are the points used by the Visible function. The plane-equation method successfully removes about 50 percent of the polygons on any object that has a definite inside and a definite outside. Remember, if you are on the inside of the object, the test must be reversed; otherwise, you won't see the object.

Unfortunately, this method can't remove all of the hidden surfaces of a 3-D object. Other methods of hidden-surface removal include the z-buffering scheme, which stores the z-coordinate (or the distance from the viewer) of every pixel on the screen in a buffer. Only the pixels closest to the viewer are kept. Another method, known as the "painter's method," is to draw all of the polygons in the correct order, back to front, so that the closer polygons cover up polygons farther from the viewer. For in-depth discussions of these and other hidden-surface removal techniques, see Christopher Lampton's Flights of Fantasy (The Waite Group Press, 1993) and Lee Adams's High Performance CAD Graphics in C (McGraw-Hill, 1989).

Optimizations

If it takes more than about one-tenth of a second to draw a side of the cube (not including floating-point calculations), the culprit is probably the two long-divide instructions that calculate GridX and GridY in the innermost loop of Project_Plane. This is because an 80286 CPU lacks the instruction to perform a full 32-bit divide, so a subroutine must be used instead. The subroutine performs the divide by shifting and subtracting the numbers, which can take hundreds of times longer than the equivalent 32-bit 80386 divide instruction. What the compiler does not know (but we do) is that the results need only be accurate to one texture-mapped pixel, which will always be in the range of 0 to 256 (or greater for larger texture maps). Therefore, you can perform a 32-by-16-bit divide to produce a 16-bit result, something the 80286 CPU can do easily. The assembly-language code to do this follows the Project_Plane function.

On a PC without a coprocessor, most of the time will be spent performing matrix operations. Try optimizing the matrix functions to use fixed-point calculations. The same optimizations can be performed in do_line. Because matrix multiplication is so basic to 3-D graphics, it is worthwhile to unroll the loops in the matrix-multiplication function.

In many applications, only 2-D graphics are needed. A 2-D image can be translated and scaled on the x- and y-axes and rotated about the z-axis without becoming three dimensional. Because projecting 2-D graphics onto the screen is a linear function and does not involve perspective, the z-coordinate will stay constant (DeltaZ will always be 0) while drawing the polygon. The formula GridX=X/Z can be rewritten as GridX=CX, where the constant C is the inverse of Z. The constant C can be removed from the loop by multiplying it by DeltaX. Performing these optimizations for 2-D graphics will remove both divides in the innermost loop, speeding up drawing immensely.

Gotchas

This program has two "gotchas." Some of the pixels at the extreme edge of the rectangle will be projected out of the range of the texture map. This is because of the inaccuracies of fixed-point arithmetic and because the screen coordinates are integers. The easiest solution to this is to not print any pixels that are projected out of range of the texture map.

Secondly, the program can only project rectangular texture maps instead of true polygons. The easiest solution to this is to have a pixel color that is clear and not print clear pixels. Another solution is to project all of the points in the polygon onto the screen and paint only within its outline.

Projects

Using the ScaleXYZ function, you can stretch the model and create reflections. For instance, ScaleXYZ (2,--1,1) will make the model stretch to twice its width on the x-axis and make a mirror reflection on the y-axis. You can also build matrices that tilt, shear, and distort the model in strange ways. For example, try removing one or more of the commented lines from Distort_Function. If you don't see the cube when experimenting with your own distort functions, you've probably moved it off the screen. If you see that the image is mapped into the polygon more than once, it's likely that the fixed-point arithmetic has overflowed. Most of the time, this problem can be corrected by making the value for FIXED_POINT smaller.

For another interesting effect, try drawing two cubes on the screen, each with slightly different positions and angles. If you cross your eyes, you can create a stereoscopic effect. Remember, the right eye views the left image; the left eye, the right image. If you switch them, or get the viewing angle or position wrong, you won't get the stereoscopic effect; you'll just get eye strain.

Figure 1 (a) Rotation of a triangle around the z-axis--the center of the triangle moves during the rotation (to prevent this, first translate the model so its center is at the origin); (b) rotation around the z-axis with origin at the center; (c) scaling a triangle not at the origin to twice its size (the model appears to move because its center is not initially at the origin; (d) scaling with origin at the center.

>Figure 2 Translation matrix.

Figure 3 Scaling matrix.

Figure 4 Transformation matrices to rotate by [theta] radians. (a) rotation around the x-axis; (b) rotation around the y-axis; (c) rotation around the z-axis.

Figure 5: (a) Calculation to perform translation, scaling, and rotation. The calculation must be performed on each point in the model; (b) using the associative property of matrix multiplication to assign a single transformation matrix with a combined effect.

(a)
      NewPoint = Translate(0,0,100)*Scale(2)*RotateX(30 Deg)
      *Translate(DCX,DCY,DCZ)*OldPoint
(b)
      T = Translate(0,0,100)*Scale(2)*RotateX(30 Degrees)
      *Translate(DCX,DCY,DCZ)

Figure 6: Projecting a point from a 2-D texture map onto the screen. The constants A, B, C, D, E, F, G, H, and I are derived from the transformation matrix. Variables x and y are the x- and y-coordinates on the texture map.

Xscreen = (AX+BY+C)/(GX+HY+I)
Yscreen = (DX+EY+F)/(GX+HY+I)

Figure 7: Coefficients of the plane can be derived from any three points on the plane with these equations.

A = Y1(Z2--Z3)+Y2(Z3--Z1)+Y3(Z1--Z2)
B = Z1(X2--X3)+Z2(X3--X1)+Z3(X1--X2)
C = X1(Y2--Y3)+X2(Y3--Y1)+X3(Y1--Y2)
D = --X1(Y2*Z3--Y3*Z2)--X2(Y3*Z1--Y1*Z3)
--X3(Y1*Z2--Y2*Z1)

Listing One


// *************************************************************************
// Texture mapping, copyright (C) 02/01/1993 by Jeremy Spiller
// This file contains the implementation for the texture mapping algorithm.
// *************************************************************************

#include <stdlib.h>
#include <iostream.h>
#include <conio.h>
#include <dos.h>
#include <math.h>
unsigned _stklen = 64000U;  // --- 64K of stack space ---

// --- Global variables ---
#define max(X,Y) ( ((X) > (Y)) ? (X) : (Y) )
#define min(X,Y) ( ((X) < (Y)) ? (X) : (Y) )
#define SCR_MAX_X 319
#define SCR_MAX_Y 199
int MX = 160, MY = 100;
int MinY, MaxY, MinX [SCR_MAX_Y+1], MaxX [SCR_MAX_Y+1];
char *Map1, *Map2, *Map3, *PhysicalScreen;

// ===== Graphics functions =====
char *Pixel_Pointer, *ScreenBuffer;
// --- Set the (X, Y) position to draw future pixels ---
#define Set_Pixel_Position(X,Y) \
    (Pixel_Pointer = ScreenBuffer + 320*(Y) + (X))
// --- Draw a pixel, then move the position to the right ---
#define Plot_Pixel_Across(Color) \
    (*Pixel_Pointer++ = (Color))
// --- Call a bios function to set the screen to graphics mode ---
void ibm_graphics_mode ()
  {
    _AH =0x00;            // --- Set video mode       ---
    _AL =0x13;            // --- Mode = VGA (320x200) ---
    geninterrupt (0x10);  // --- Perform mode change  ---
  }
// --- Calls a bios function to set the screen to text mode ---
void ibm_text_mode ()
  {
    _AH =0x00;            // --- Set video mode       ---
    _AL =0x03;            // --- Mode = text          ---
    geninterrupt (0x10);  // --- Perform mode change  ---
  }
// ===== MAT3D - Matrix functions for 3D matrix operations. This is a standard
//    mathematical matrix, accessed as M [Row][Column] (or M [Y][X]) where
//    M[0][0] is the first element.
class MAT_COLUMN
  {
    double Column [4];
    public:
    double &operator [] (int Index)
      {
        return Column [Index];
      }
  };
// --- 3D matrix class ---
class MAT3D
  {
    MAT_COLUMN Matrix [4];
    public:
    MAT_COLUMN &operator [] (int Index)
      {
        return Matrix [Index];
      }
    friend MAT3D operator * (MAT3D &A, MAT3D &B);
  };
// --- Multiply two matrices, returns A*B ---
MAT3D operator * (MAT3D &A, MAT3D &B)
  {
    int I, J, K;
    MAT3D Temp;
    for (I = 0;  I < 4;  I++)
      for (J = 0;  J < 4;  J++)
        {
         double Sum = 0;
         for (K = 0;  K < 4;  K++)
           Sum += A [I][K] * B [K][J];
         Temp [I][J] = Sum;
        }
    return Temp;
  }
// --- Produce an identity matrix ---
MAT3D Identity ()
  {
    int X, Y;
    MAT3D Temp;
    for (Y = 0;  Y < 4;  Y++)
      for (X = 0;  X < 4;  X++)
    if (X == Y)
      Temp [Y][X] = 1;
    else
      Temp [Y][X] = 0;

    return Temp;
  }
// --- Calculate pitch - rotation around the X axis ---
MAT3D RotateX (double Angle)
  {
    MAT3D Temp = Identity ();
    Temp [1][1] = Temp [2][2] = cos (Angle);
    Temp [2][1] = - ( Temp [1][2] = sin (Angle) );
    return Temp;
  }
// --- Calculate yaw - rotation around the Y axis ---
MAT3D RotateY (double Angle)
  {
    MAT3D Temp = Identity ();
    Temp [0][0] = Temp [2][2] = cos (Angle);
    Temp [0][2] = - ( Temp [2][0] = sin (Angle) );
    return Temp;
  }
// --- Calculate roll - rotation around the Z axis ---
MAT3D RotateZ (double Angle)

  {
    MAT3D Temp = Identity ();
    Temp [0][0] = Temp [1][1] = cos (Angle);
    Temp [1][0] = - ( Temp [0][1] = sin (Angle) );
    return Temp;
  }
// --- Calculate pitch, yaw, and roll ---
MAT3D RotateXYZ (double AngleX, double AngleY, double AngleZ)
  {
    return RotateZ (AngleZ) * RotateY (AngleY) * RotateX (AngleX);
  }
// --- Calculate Scale - enlarge or shrink model (about origin) ---
MAT3D Scale (double Scale)
  {
    MAT3D Temp = Identity ();
    Temp [0][0] = Temp [1][1] = Temp [2][2] = Scale;
    return Temp;
  }
// --- Calculate Scale for each axis (can cause distortion) ---
MAT3D ScaleXYZ (double SX, double SY, double SZ)
  {
    MAT3D Temp = Identity ();
    Temp [0][0] = SX;
    Temp [1][1] = SY;
    Temp [2][2] = SZ;
    return Temp;
  }
// --- Translate - move model ---
MAT3D Translate (double X, double Y, double Z)
  {
    MAT3D Temp = Identity ();
    Temp [0][3] = X;
    Temp [1][3] = Y;
    Temp [2][3] = Z;
    return Temp;
  }
// --- Translate a point through a matrix, returns M * [X, Y, Z, 1]t ---
void Translate_Point (MAT3D &M, double &X, double &Y, double &Z)
  {
    double TX = X*M[0][0] + Y*M[0][1] + Z*M[0][2] + M[0][3];
    double TY = X*M[1][0] + Y*M[1][1] + Z*M[1][2] + M[1][3];
    double TZ = X*M[2][0] + Y*M[2][1] + Z*M[2][2] + M[2][3];
    X = TX;
    Y = TY;
    Z = TZ;
  }
// -- Draw a line clipped on Y axis, and has only one point per scan line. --
void do_line (void Plot(long X,long Y),double X1,double Y1,double X2,double Y2)
  {
    double YL = 0, YH = 200, Temp, DeltaX;
    if ((Y1 < YL && Y2 < YL) || (Y1 > YH && Y2 > YH))
      return;
    // --- if Y1 > Y2, swap (X1, Y1) with (X2, Y2) ---
    if (Y1 > Y2)
      {
        Temp = Y1;
        Y1 = Y2;
        Y2 = Temp;
        Temp = X1;
        X1 = X2;
        X2 = Temp;
      }
    // --- Is this a horizontal line? ---
    if (Y2 - Y1 == 0)
      {
        Plot (X1, Y1);
        Plot (X2, Y2);
        return;
      }
    DeltaX = (X2 - X1) / (Y2 - Y1);
    // --- Clip points ---
    if (Y1 < YL)
      {
        X1 = X1 + (YL - Y1) * DeltaX;
        Y1 = YL;
      }
    if (Y2 > YH)
      {
        X2 = X2 + (YH - Y2) * DeltaX;
        Y2 = YH;
      }
    // --- Draw line ---
    while (Y1 <= Y2)
      {
        Plot (X1, Y1);
        X1 += DeltaX;
        Y1 += 1;
      }
  }
// --- Find the minimum and maximum bounds of the plane ---
void MinMaxPixel (long X, long Y)
  {
    if (Y >= 0 && Y < SCR_MAX_Y)
      {
        MinX [Y] = min (MinX [Y], X);
        MaxX [Y] = max (MaxX [Y], X);
      }
  }
// --- Fill up the min/max outline ---
void Find_Outline (MAT3D &M, double Xp, double Yp)
  {
    double P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y;
    double Z1 = 0, Z2=0, Z3=0, Z4=0;
    int CountY;
    MaxY = 0;
    MinY = SCR_MAX_Y;
    // --- Initialize array values ---
    for (CountY = 0;  CountY < SCR_MAX_Y;  CountY++)
      {
        MinX [CountY] =  32000;
        MaxX [CountY] = -32000;
      }
    // --- Project four corners ---
    P1x = 0;  P1y = 0;
    P2x = Xp;  P2y = 0;
    P3x = Xp;  P3y = Yp;
    P4x = 0;  P4y = Yp;
    Translate_Point (M, P1x, P1y, Z1);
    Translate_Point (M, P2x, P2y, Z2);
    Translate_Point (M, P3x, P3y, Z3);
    Translate_Point (M, P4x, P4y, Z4);
    // --- Projection formula ---
    P1x = P1x/fabs(Z1) + MX;
    P1y = P1y/fabs(Z1) + MY;
    P2x = P2x/fabs(Z2) + MX;
    P2y = P2y/fabs(Z2) + MY;
    P3x = P3x/fabs(Z3) + MX;
    P3y = P3y/fabs(Z3) + MY;
    P4x = P4x/fabs(Z4) + MX;
    P4y = P4y/fabs(Z4) + MY;
    // --- Calculate min and max values of outline ---
    do_line (MinMaxPixel, P1x, P1y, P2x, P2y);
    do_line (MinMaxPixel, P2x, P2y, P3x, P3y);
    do_line (MinMaxPixel, P3x, P3y, P4x, P4y);
    do_line (MinMaxPixel, P4x, P4y, P1x, P1y);
  }

// ==== Project a texture map onto the screen.  The size of the map is
// [0..Xp] [0..Yp]. It will be transformed through the matrix M,
// translated by the variables MX and MY, and drawn on the screen.

void Project_Plane (MAT3D &M, char *Map, int Xp, int Yp)
  {

    int GridX, GridY;
    long X, Y, Z, DeltaX, DeltaY, DeltaZ;
    int Ypos, Xpos, MaxXpos, LineLength;
    Find_Outline (M, Xp, Yp);
    // --- Calculate inverse of M * [X, Y, 0, 1]t ---
    double Sx1 = M [0][0], Sy1 = M [0][1], T1 = M [0][3];
    double Sx2 = M [1][0], Sy2 = M [1][1], T2 = M [1][3];
    double Sx3 = M [2][0], Sy3 = M [2][1], T3 = M [2][3];
    const long FIXED_POINT = 64;
    // --- Calculate X axis (Scale X) ---
    long SXx = (T2*Sy3 - T3*Sy2) * FIXED_POINT;   // Scale X
    long SXy = (T3*Sy1 - T1*Sy3) * FIXED_POINT;   // Scale Y
    long SXt = (T1*Sy2 - T2*Sy1) * FIXED_POINT;   // Translate
    // --- Calculate Y axis (Scale Y) ---
    long SYx =  (T3*Sx2 - T2*Sx3) * FIXED_POINT;  // Scale X
    long SYy =  (T1*Sx3 - T3*Sx1) * FIXED_POINT;  // Scale Y
    long SYt =  (T2*Sx1 - T1*Sx2) * FIXED_POINT;  // Translate
    // --- Calculate Z axis (Scale Z) ---
    long SZx = (Sx3*Sy2 - Sx2*Sy3) * FIXED_POINT;   // Scale X
    long SZy = (Sx1*Sy3 - Sx3*Sy1) * FIXED_POINT;   // Scale Y
    long SZt = (Sx2*Sy1 - Sx1*Sy2) * FIXED_POINT;   // Translate
    for (Ypos = 0;  Ypos < SCR_MAX_Y;  Ypos += 1)
      {
        Xpos = max (0, MinX [Ypos]);
        MaxXpos = min (SCR_MAX_X, MaxX [Ypos]);
        LineLength = MaxXpos - Xpos + 1;
        if (Xpos <= MaxXpos)
          {
            X = ((Xpos-MX)*SXx + (Ypos-MY)*SXy + SXt);
            DeltaX = SXx;
            Y = ((Xpos-MX)*SYx + (Ypos-MY)*SYy + SYt);
            DeltaY = SYx;
            Z = ((Xpos-MX)*SZx + (Ypos-MY)*SZy + SZt) | 1; //make z odd
            DeltaZ = SZx & ~1;    // --- Force Z to stay odd ---
            Set_Pixel_Position (Xpos, Ypos);
            while (--LineLength >= 0)
              {
                GridX = X / Z;
                GridY = Y / Z;
                X += DeltaX;
                Y += DeltaY;
                Z += DeltaZ;
                Plot_Pixel_Across (Map [256*GridY + GridX]);
              }
          }
      }
  }
// -- If you are compiling this for a 286 machine, or with a 286 compiler, 
//     replace the two lines (GridX = X/Z) and (GridY = Y/Z) with the following
//     assembly code. This will speed up the process by using a machine 
//     language divide instruction instead of a subroutine.  
//      asm   mov    al, byte ptr Y+3
//      asm   cbw
//      asm   mov    dx, ax
//      asm   mov    cx, word ptr Z+1
//      asm   mov    ax, word ptr Y+1
//      asm   idiv   cx
//      asm   mov    word ptr GridY, ax;
//
//      asm   mov    al, byte ptr X+3
//      asm   cbw
//      asm   mov    dx, ax
//      asm   mov    cx, word ptr Z+1
//      asm   mov    ax, word ptr X+1
//      asm   idiv   cx
//      asm   mov    word ptr GridX, ax

// --- Is the viewer on the visible (or invisible) side of the plane? ---
int Visible (MAT3D &M)
  {
    // --- Point1 = M * [0, 0, 0, 1]t ---
    double X1 = M [0][3];
    double Y1 = M [1][3];
    double Z1 = M [2][3];
    // --- Point2 = M * [0, 1, 0, 1]t ---
    double X2 = M [0][1] + M [0][3];
    double Y2 = M [1][1] + M [1][3];
    double Z2 = M [2][1] + M [2][3];
    // --- Point3 = M * [1, 0, 0, 1]t ---
    double X3 = M [0][0] + M [0][3];
    double Y3 = M [1][0] + M [1][3];
    double Z3 = M [2][0] + M [2][3];
    double D = -X1*(Y2*Z3-Y3*Z2) - X2*(Y3*Z1-Y1*Z3) - X3*(Y1*Z2-Y2*Z1);
    // --- If the viewer is on the positive side, the plane is visible ---
    return D > 0;
  }
// --- Return a matrix that distorts the model ---
MAT3D Distort_Function ()
  {
    MAT3D Distort = Identity ();
    // Distort [0][0] = 2;                      // Stretch on X axis
    // Distort [1][0] = Distort [0][1] = .4;    // Parallelogram
    // Distort [3][1] = .005;                   // Pyramid
    return Distort;
  }
// --- rotate a cube ---
main ()
  {
    int I;
    long Fx, Fy;
    // --- Set up memory ---
    PhysicalScreen = (char *)MK_FP (0xA000, 0x0000);
    ScreenBuffer = new char [320U*200];
  Map1 = new char [256U*200];
    if (!( Map2 = new char [256U*200] )) Map2 = Map1;
    if (!( Map3 = new char [256U*200] )) Map3 = Map1;
    if (ScreenBuffer == 0 || Map1 == 0)
      {
        cout << "Sorry, not enough memory to run this program!\n";
        exit (1);
      }
    // --- Draw a picture of crosses (X*Y) ---
    for (Fx = 0;  Fx < 256;  Fx++)
      for (Fy = 0;  Fy < 199;  Fy++)
  Map1 [256*Fy + Fx] = (Fx - 128) * (Fy - 100) / 256 + 128;
    // --- Draw a picture of circles (X*X + Y*Y) ---
    for (Fx = 0;  Fx < 256;  Fx++)
      for (Fy = 0;  Fy < 199;  Fy++)
  {
      long Dx = (Fx - 128);
      long Dy = (Fy - 100);
      Map2 [256*Fy + Fx] = (Dx*Dx + Dy*Dy) / 128 + 16;
  }
    // --- Draw some lines (X) ---
    for (Fx = 0;  Fx < 256;  Fx++)
      for (Fy = 0;  Fy < 199;  Fy++)
  Map3 [256*Fy + Fx] = Fx + 17;
     // --- Build 6 sides of a cube relative to the origin ---
     MAT3D Flip, S1, S2, S3, S4, S5, S6;
     Flip = RotateY (180 * 3.141592654 / 180);
     Flip = Translate (256, 0, 0) * Flip;
     S1 = Translate (0, 0, 0);
     S2 = Flip;
     S2 = RotateX (-90 * 3.141592654 / 180) * S2;
     S3 = Flip;
     S3 = Translate (-56, 0, 0) * S3;
     S3 = RotateY (90 * 3.141592654 / 180) * S3;
     S4 = Flip;
     S4 = Translate (0, 0, 200) * S4;
     S5 = RotateX (-90 * 3.141592654 / 180);
     S5 = Translate (0, 200, 0) * S5;
     S6 = RotateY (90 * 3.141592654 / 180);
     S6 = Translate (256, 0, 0) * S6;
     ibm_graphics_mode ();
     for (I = 0;  I < 20000;  I++)
       {
         // --- Move center of cube to origin, rotate cube, move   ---
         // --- cube back (away from viewer), and scale for screen ---
         MAT3D Cube = Translate (-128, -100, -100);
         Cube = Distort_Function () * Cube;
         Cube = RotateXYZ (I*0.051, I*0.01, I*0.037) * Cube;
         Cube = Translate (0, 0, sin (-I*0.0121) * 350 + 350 + 400) * Cube;
         Cube = ScaleXYZ (230, 200, 1) * Cube;  // --- scale for screen ---
         // --- Transform each side of the cube ---
         MAT3D Side1 = Cube*S1;
         MAT3D Side2 = Cube*S2;
         MAT3D Side3 = Cube*S3;
         MAT3D Side4 = Cube*S4;
         MAT3D Side5 = Cube*S5;
         MAT3D Side6 = Cube*S6;
         // --- Draw each side of the cube (if visible) ---
         memset (ScreenBuffer, 0, 320U*200);  // Clear screen buffer
         if (Visible (Side1))
           Project_Plane (Side1, Map1, 255, 199);
         if (Visible (Side2))
           Project_Plane (Side2, Map2, 255, 199);
         if (Visible (Side3))
           Project_Plane (Side3, Map3, 199, 199);
         if (Visible (Side4))
           Project_Plane (Side4, Map1, 255, 199);
         if (Visible (Side5))
           Project_Plane (Side5, Map2, 255, 199);
         if (Visible (Side6))
           Project_Plane (Side6, Map3, 199, 199);
         memcpy (PhysicalScreen, ScreenBuffer, 320U*200);  // Copy to screen
         if (kbhit ()) if (getch () == 27) break;  // Break at users request
       }
    ibm_text_mode ();
    cout << "Texture mapped cube copyright (C) 1993 by Jeremy Spiller.\n";
  }


Copyright © 1994, Dr. Dobb's Journal

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