_RaveKit: A Portable Graphics Toolkit_
by Mark Carolan

Listing One     
void Raver::TimeSlice(long keys)
{
    TQAVGouraud v1, v2, v3;
    TQAVTexture t1, t2, t3;
    TQATexture *tex = NULL;
    Tri3D *this_tri = tri_list;

    // The generic Renderer base class handles moving the 3D "world". The 
        // vertex objects within the world take care of "projecting" the 
        // visible vertices of that world onto a 2D viewport and the Tri3D 
        // class contains the Gouraud shading and/or texture info
    
    // call base class, handle vertex transformations
    // according to input info in keys bitfield 
    Renderer::MoveWorld(keys); 
   
    QARenderStart (drawContext, NULL, cache); 
    // sadly, no cache cap in default engine
    
    while (this_tri) {
        // In this example, clipping is only at the triangle level. With a bit
        // of edge checking, the gaps could be filled in with a maximum of 
        // 2-for-1 triangles (3-for-1 max on corners)
        // uv co-ords also need to be offset        
        Vertex3D &v3a = vertex_list[this_tri->vi1];
        Vertex3D &v3b = vertex_list[this_tri->vi2];
        Vertex3D &v3c = vertex_list[this_tri->vi3];
        if (this_tri->GetClip(vertex_list)) {
            if (this_tri->texmap > 0 && 
                this_tri->texmap < num_textures) {
                tex = texTable[this_tri->texmap].tp;
                if (tex) {  
                    QASetPtr(drawContext, 
                        kQATag_Texture, tex);
                    t1.x = v3a.pix_point.x;
                    t1.y = v3a.pix_point.y;
                    t1.z = v3a.invZ; 
                    t1.invW = v3a.invZ;;
                    t1.a = 1.0f;
                    t1.r = 1.0f;
                    t1.g = 1.0f;
                    t1.b = 1.0f;
                    t1.uOverW = v3a.u;
                    t1.vOverW = v3a.v;

                    t2.x = v3b.pix_point.x;
                    t2.y = v3b.pix_point.y;
                    t2.z = v3b.invZ; 
                    t2.invW = v3b.invZ;
                    t2.a = 1.0f;
                    t2.r = 1.0f;
                    t2.g = 1.0f;
                    t2.b = 1.0f;
                    t2.uOverW = v3b.u;
                    t2.vOverW = v3b.v;

                    t3.x = v3c.pix_point.x;
                    t3.y = v3c.pix_point.y;
                    t3.z = v3c.invZ; 
                    t3.invW = v3c.invZ;
                    t3.a = 1.0f;
                    t3.r = 1.0f;
                    t3.g = 1.0f;
                    t3.b = 1.0f;
                    t3.uOverW = v3c.u;
                    t3.vOverW = v3c.v;
                    QADrawTriTexture(drawContext, &t1, &t2, &t3, kQATriFlags_None);
                }
           }           
            else {
                v1.x = v3a.pix_point.x;
                v1.y = v3a.pix_point.y;
                v1.z = v3a.invZ; 
                v1.invW = 1;
                v1.a = 1.0f;

                v2.x = v3b.pix_point.x;
                v2.y = v3b.pix_point.y;
                v2.z = v3b.invZ; 
                v2.invW = 1;
                v2.a = 1.0f;

                v3.x = v3c.pix_point.x;
                v3.y = v3c.pix_point.y;
                v3.z = v3c.invZ; 
                v3.invW = 1;
                v3.a = 1.0f;
                
                if (this_tri->type == TRI_MOVEABLE) { 
                    // calc dynamic lighting here
                    // no-op for this demo
                }
                else {
                    v1.r = this_tri->rgb1.r;
                    v1.g = this_tri->rgb1.g;
                    v1.b = this_tri->rgb1.b;
                    
                    v2.r = this_tri->rgb2.r;
                    v2.g = this_tri->rgb2.g;
                    v2.b = this_tri->rgb2.b;
                    
                    v3.r = this_tri->rgb3.r;
                    v3.g = this_tri->rgb3.g;
                    v3.b = this_tri->rgb3.b;
                }
                QADrawTriGouraud(drawContext, &v1, &v2, &v3, kQATriFlags_None);
            }
        }
        this_tri = this_tri->next;
    }       
    QARenderEnd (drawContext, NULL);        
}


Listing Two
int Vector3D::Project()
{   
    if (z <= view_plane) return -1000;
    invZ = 1.0f - view_plane / z;   
    scaleZ = scaleZX / z;
    pix_point.x = (int)(raster_centerX + x * scaleZ + 0.5f);
    pix_point.y = (int)(raster_centerY + y * scaleZ + 0.5f);    
   return ClipBounds();
}

Listing Three
// Renderer::MoveWorld() - called by derived class. Processes user input and 
// sets up transformation & rotation values which it passes to TransformWorld()

void Renderer::TransformWorld(
    float tx, float ty, float tz, 
    float rx, float ry, float rz)
{
    for (int n = 0; n < num_vertices; n++)
        vertex_list[n].Transform(tx, ty, tz,    
            rx, ry, rz, T_PREROTATION | T_RELATIVE);
}

Listing Four
// Snippet from Vector3D::Transform()
// ...
    else if (t & T_RELATIVE) {  
        if(t&T_PREROTATION){    
            x += tx;
            y += ty;
            z += tz;        
            Rotate(rx, ry, rz);
        }
// ...
// The rotate component of Transform(). Defined as inline
void Vector3D::Rotate(float rx, float ry, float rz)
{
    float sin_a, cos_a, temp;
    if (ry) {
        sin_a = sin(ry);
        cos_a = cos(ry);                
        temp = x * cos_a - z * sin_a;       
        z = x * sin_a + z * cos_a; 
        x = temp;
    }           
    if (rx) {
        sin_a = sin(rx);
        cos_a = cos(rx);
        temp = y * cos_a - z * sin_a;       
        z = y * sin_a + z * cos_a;
        y = temp;
    }
    if (rz) {
        sin_a = sin(rz);
        cos_a = cos(rz);                
        temp = x * cos_a - y * sin_a;       
        y = x * sin_a + y * cos_a; 
        x = temp;
    }               
}

Listing Five
// Triangles are added to the linked list as indexes into the vertex array. 
// Although RAVE has built-in support for meshs, I've kept it abstracted from 
// the renderer. In Direct3D IM, for example, you can pass your own vertex 
// index list with stride info and so avoid duplication of services
void Renderer::AddTri(
    int vi1, int vi2, int vi3,  // vertex lookup indices
    Uv uv1, Uv uv2, Uv uv3,     // texture coords
    Rgb &p_rgb,                 // the "center" color
    int tmp, int type)          // texmap index, type "hint"
{
    Tri3D *active_tri, *new_tri;
    active_tri = tri_list;
    while (active_tri) {
        if (active_tri->next)
            active_tri = active_tri->next;
        else
            break;  
    }
    new_tri = new Tri3D(
    vi1, vi2, vi3, uv1, uv2, uv3, p_rgb, tmp, type, 
        lightList, vertex_list);    
    if (!active_tri) // must be first one
        tri_list = new_tri;
    else
        active_tri->next = new_tri;     
}
// Vertexes are stored in an array for fast random lookup
int Renderer::AddVertex(
    float x, float y, float z, 
    float r, float g, float b)
{
    if (num_vertices >= MAX_VERTICES) 
        // allocate some more storage here
        // ...
        return -1;
    Vertex3D &vert = vertex_list[num_vertices++];
    vert.Install(x, y, z, r, g, b);
    return num_vertices - 1;    
}


Listing Six
int Renderer::CreateTransformPanel( 
    float width, float height, 
    int mesh_width, int mesh_height,
    float rcol, float gcol, float bcol,
    float rx, float ry, float rz,
    float tx, float ty, float tz,
    int tex_index)
{
    float posx = -(mesh_width / 2.0f);
    float posy = -(mesh_height / 2.0f);
    float posz = 0;
    float vx, vy, vz;
    int q1, q2, q3, q4;
    float colsize = width / mesh_width;
    float rowsize = height / mesh_height;
    int firstv = num_vertices, lastv = -1, ext;
    float slope, s = mesh_width * mesh_height;
    Rgb t_rgb(rcol, gcol, bcol);
        
    // make a simple mesh   
    for (int row = 0; row < mesh_height; row ++) {
        for (int col = 0; col < mesh_width; col++) {
            slope = s - (float)(row * mesh_width + col) + GIVE;
            vx = posx + col * colsize;
            vy = posy + row * rowsize;
            vz = posz;
            if ((lastv = AddVertex(vx, vy, vz, 
                rcol * fmax((slope / s), 1.0f), 
                gcol * fmax((slope / s), 1.0f), 
                bcol * fmax((slope / s), 1.0f))) < 0)
                goto skip;
            TransformVertex(lastv, tx, ty, tz, rx, ry, rz);
        }       
    }
skip:   
    for (int row = 0; row < mesh_height - 1; row++) {
        int offset = firstv + row * mesh_width;
        for (int col = 0; col < mesh_width - 1; col++) {
            if ((ext = (offset + col + mesh_width + 1)) > lastv) 
                return 0;
            q1 = offset + col;
            q2 = offset + col + mesh_width;
            q3 =  ext;
            q4 = offset + col + 1;
            AddQuad(q1, q2, q3, q4, t_rgb, tex_index);
        }
    }
    return 1;
}
int Renderer::LoadDXFTris(
    char *fileName, int tex_index, float scale, 
    float r, float g, float b,
    float tx, float ty, float tz,
    float rx, float ry, float rz)
{
    // NOTE: ignores all but the triangle list within the DXF file
    FILE *fp = fopen(fileName, "r");
    float v1, v2, v3;
    int firstv = num_vertices, lastv = -1, v_loaded = 0;
    char buff[128];
    int mesh_width = 0, mesh_height = 0, ext;
    int q1, q2, q3, q4;
    Rgb t_rgb(r, g, b);
    
    if (fp) {
seq_begin: 
   while (fgets(buff, 128, fp)) {
       if (strstr(buff, "POLYLINE")) {
         while (fgets(buff, 128, fp)) {
           if (strstr(buff, " 70")) {
             fgets(buff, 128, fp); 
             // eat next line
             fscanf(fp, " 71 %d 72 %d", &mesh_height, &mesh_width);
             while (fgets(buff, 128, fp)){               
              if (strstr(buff, "VERTEX")) {
               if (fgets(buff, 128, fp) && fgets(buff, 128, fp)) {
                if (fscanf(fp, " 10 %f 20 %f 30 %f", &v1, &v2, &v3) == 3) {
                  if ((lastv = AddVertex(v1 * scale, v2 * scale, v3 * scale, 
                                                                    r, g,b))<0)
                            goto  add_quads;
                         TransformVertex(lastv, tx, ty, tz, rx, ry, rz); 
                       v_loaded++;
                     }
                   }
                 }
                 else if (strstr(buff, "SEQEND"))
                     goto seq_begin;
             }
           }
         }
       }
   }       
add_quads:
        fclose(fp);     
        if (v_loaded) {
            for (int row = 0; row < mesh_height - 1; row++) {
                int offset = firstv + row * mesh_width;
                for (int col = 0; col < mesh_width - 1; col++) {
                    if ((ext = (offset + col + mesh_width + 1)) > lastv) 
                        return 0;
                    q1 = offset + col;
                    q2 = offset + col + mesh_width;
                    q3 =  ext;
                    q4 = offset + col + 1;
                    AddQuad(q1, q2, q3, q4, t_rgb, tex_index);
                }
            }
            return 1;
        }
    }
    return 0;
}

Listing Seven
#if defined(macintosh)
int main()
#elif defined(__INTEL__)
int CALLBACK WinMain
(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
)
#endif
{   
    unsigned long scanrslt;
#if defined(macintosh)
    InitToolbox ();
#endif  
    KeyScanner scanner(&bscan);
    is_active = 1;
#if defined(__INTEL__)
    MSG msg;
    if (!InitApplication(hInstance)) return (FALSE);    
    rave = new Raver(WINWIDTH, WINHEIGHT, (void*)hInstance);
    rave->Load();
    while (running) {
        if (::PeekMessage(&msg, rave->window, 0, 0, PM_REMOVE))
                ::DispatchMessage(&msg);                
            scanner.Scan(&scanrslt);
            if (scanrslt & k_esc)
                running = 0;                            
            else        
            if (is_active) rave->TimeSlice(scanrslt);       
    }
    return (msg.wParam); // INTEL
#elif defined(macintosh)
    EventRecord event;
    // Compiler won't allow >32K dynamic class on Mac (local data error)
    // So create pointer
    rave = new Raver(WINWIDTH, WINHEIGHT, NULL);
    if (rave) {
        rave->Load();
        while (running) {
            if (WaitNextEvent(keyDownMask, &event, 0, NULL))
                HandleEvent(&event);                
            else {
                scanner.Scan(&scanrslt);
                if (scanrslt & k_esc) 
                    running = 0;       
                else
                    rave->TimeSlice(scanrslt);
            }
        }   
    }   
    FlushEvents (everyEvent, 0);
    return (0);
#endif // macintosh
}

Listing Eight
// The "fake" lighting model. The light object is an alias of the Vector3D 
// object, and so uses some of the built-in features of Vector3D to provide 
// lighting information to the Tri3D object; namely the 
// Vector3D::GetStrength() member function, which in turn uses 
// Vector3D::GetDotProduct() and Vector3D::GetDistance() 
// in a fairly arbitrary way I should add...

void Tri3D::CalcFixedLightEffects(
    Light3D lightList[], Vertex3D &v1, Vertex3D &v2, Vertex3D &v3)
{
    lightEffect_1 = lightEffect_2 = lightEffect_3 = 0.0f;
    int active_lights = 0;
    float dotPlus = 1.0f;
                        
    for (int i = 0; i < MAX_LIGHTS; i++) {
        Light3D &l3d = lightList[i];
        if (l3d.active) { 
            active_lights++;
            switch (l3d.light_type) {
            case L3D_SPOT:
            case L3D_AMBIENT:
            case L3D_LOCAL:
            case L3D_DISTANT:
                // GetStrength should return val between -1.0f              
                // and 1.0f based on dot product of light vector
                // and tri normal, multiplied by distance for spot or local, or
                // scaled by relative distance from nearest point of object
                // It's a fair hack, anyway...          
                float dis_1 = v1.GetDistance(l3d); 
                float dis_2 = v2.GetDistance(l3d); 
                float dis_3 = v3.GetDistance(l3d);
                
                // invert
                dis_1 = 1.0f / dis_1;
                dis_2 = 1.0f / dis_2; 
                dis_3 = 1.0f / dis_3; 
                float dot = normal.GetDotProduct(l3d);
                if (dot != 0.0f) { 
                    // workaround MW debugger problem no fp
                    // watch (fixed in ver 11)
                    dotPlus = (1.0f - dot) * l3d.strength;              
                }
                lightEffect_1 += dis_1 + dotPlus;
                lightEffect_2 += dis_2 + dotPlus;
                lightEffect_3 += dis_3 + dotPlus;
            }
        }
    }
    if (active_lights) {
        lightEffect_1 /= active_lights;
        lightEffect_2 /= active_lights;

       lightEffect_3 /= active_lights;
        
        rgb1.r = flimit(r + lightEffect_1, 0.0f, 1.0f);
        rgb1.g = flimit(g + lightEffect_1, 0.0f, 1.0f);
        rgb1.b = flimit(b + lightEffect_1, 0.0f, 1.0f);

        rgb2.r = flimit(r + lightEffect_2, 0.0f, 1.0f);
        rgb2.g = flimit(g + lightEffect_2, 0.0f, 1.0f);
        rgb2.b = flimit(b + lightEffect_2, 0.0f, 1.0f);

        rgb3.r = flimit(r + lightEffect_3, 0.0f, 1.0f);
        rgb3.g = flimit(g + lightEffect_3, 0.0f, 1.0f);
        rgb3.b = flimit(b + lightEffect_3, 0.0f, 1.0f);     
    }
}




