_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); } }