/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : ww3d * * * * $Archive:: /Commando/Code/ww3d2/visrasterizer.cpp $* * * * Original Author:: Greg Hjelstrom * * * * $Author:: Jani_p $* * * * $Modtime:: 11/24/01 5:42p $* * * * $Revision:: 11 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "visrasterizer.h" #include "camera.h" #include "plane.h" #include "vp.h" /** ** VisPolyClass - This class is used to clip polygons as they are ** sent through the vis rasterization system */ class VisPolyClass { public: void Reset(void); void Add_Vertex(const Vector3 & point); void Clip(const PlaneClass & plane,VisPolyClass & dest) const; SimpleDynVecClass Verts; }; void VisPolyClass::Reset(void) { Verts.Delete_All(false); } void VisPolyClass::Add_Vertex(const Vector3 & point) { Verts.Add(point); } void VisPolyClass::Clip(const PlaneClass & plane,VisPolyClass & dest) const { dest.Reset(); // temporary variables used in clipping int i = 0; int vcount = Verts.Count(); int iprev = vcount - 1; bool cur_point_in_front; bool prev_point_in_front; float alpha; Vector3 int_point; if (vcount <= 2) return; // perform clipping prev_point_in_front = !plane.In_Front(Verts[iprev]); // Note, plane normal is outward so we invert this test for (int j=0; j=vcount) { i = 0; } } } static VisPolyClass _VisPoly0; static VisPolyClass _VisPoly1; /********************************************************************************************* VisRasterizerClass Implementation *********************************************************************************************/ VisRasterizerClass::VisRasterizerClass(void) : ModelTransform(1), Camera(NULL), MVTransform(1) { } VisRasterizerClass::~VisRasterizerClass(void) { REF_PTR_RELEASE(Camera); } void VisRasterizerClass::Set_Resolution(int width,int height) { IDBuffer.Set_Resolution(width,height); } void VisRasterizerClass::Get_Resolution(int * set_width,int * set_height) { IDBuffer.Get_Resolution(set_width,set_height); } void VisRasterizerClass::Set_Model_Transform(const Matrix3D & model) { ModelTransform = model; Update_MV_Transform(); } void VisRasterizerClass::Set_Camera(CameraClass * camera) { REF_PTR_SET(Camera,camera); Update_MV_Transform(); } CameraClass * VisRasterizerClass::Get_Camera(void) { if (Camera != NULL) { Camera->Add_Ref(); } return Camera; } CameraClass * VisRasterizerClass::Peek_Camera(void) { return Camera; } void VisRasterizerClass::Update_MV_Transform(void) { Matrix3D view_tm(1); if (Camera) { Camera->Get_View_Matrix(&view_tm); } MVTransform = view_tm * ModelTransform; } const Matrix3D & VisRasterizerClass::Get_MV_Transform(void) { // TODO: optimize this Update_MV_Transform(); // the user can and does mess with the camera directly! return MVTransform; } Vector3 * VisRasterizerClass::Get_Temp_Vertex_Buffer(int count) { TempVertexBuffer.Uninitialised_Grow(count); return &(TempVertexBuffer[0]); } bool VisRasterizerClass::Render_Triangles ( const Vector3 * verts, int vcount, const TriIndex * tris, int tcount,const AABoxClass & bounds ) { WWASSERT(verts != NULL); WWASSERT(tris != NULL); WWASSERT(vcount > 0); WWASSERT(tcount > 0); /* ** if the user supplied bounds, check if we need to clip */ if (CollisionMath::Overlap_Test(Camera->Get_Frustum(),bounds) == CollisionMath::INSIDE) { return Render_Triangles_No_Clip(verts,vcount,tris,tcount); } else { return Render_Triangles_Clip(verts,vcount,tris,tcount); } } bool VisRasterizerClass::Render_Triangles_No_Clip ( const Vector3 * verts, int vcount, const TriIndex * tris, int tcount ) { bool pixel_passed = false; /* ** 1. transform verts into homogeneous view space and project */ const Matrix3D & tm = Get_MV_Transform(); Vector3 * tverts = Get_Temp_Vertex_Buffer(vcount); VectorProcessorClass::Transform(tverts,verts,tm,vcount); for (int i=0; iProject_Camera_Space_Point(tverts[i],tverts[i]); } /* ** 2. Pass triangles on to the ID buffer for scan conversion */ for (int tri_index=0; tri_indexGet_View_Space_Frustum_Planes(); /* ** 3. Clip triangles to the view volume and pass on to the ID buffer for scan conversion */ for (int tri_index=0; tri_index= 3) { Vector3 * final_verts = &(_VisPoly0.Verts[0]); int i; for (i=0; iProject_Camera_Space_Point(final_verts[i],final_verts[i]); } /* ** Pass the resulting triangle fan to the IDBuffer */ for (i=1; i 0) { IDBuffer = new uint32 [bufsize]; ZBuffer = new float [bufsize]; } } void IDBufferClass::Clear(void) { if ((ResWidth > 0) && (ResHeight > 0)) { int byte_count = ResWidth * ResWidth * sizeof(uint32); WWASSERT(IDBuffer != NULL); WWASSERT(ZBuffer != NULL); memset(IDBuffer,0,byte_count); memset(ZBuffer,0,byte_count); } } /** ** GradientsStruct ** Computes the gradients for a triangle. */ struct GradientsStruct { GradientsStruct(const Vector3 * verts) { float oodx = 1 / ( ((verts[1].X - verts[2].X) * (verts[0].Y - verts[2].Y)) - ((verts[0].X - verts[2].X) * (verts[1].Y - verts[2].Y))); float oody = -oodx; for(int i=0; i<3; i++) { OOZ[i] = 1/verts[i].Z; } DOOZ_DX = oodx * ( ((OOZ[1] - OOZ[2]) * (verts[0].Y - verts[2].Y)) - ((OOZ[0] - OOZ[2]) * (verts[1].Y - verts[2].Y))); DOOZ_DY = oody * ( ((OOZ[1] - OOZ[2]) * (verts[0].X - verts[2].X)) - ((OOZ[0] - OOZ[2]) * (verts[1].X - verts[2].X))); } float OOZ[3]; // 1/z for each vertex float DOOZ_DX; // change in 1/z per change in x float DOOZ_DY; // change in 1/z per change in y }; /** ** EdgeStruct ** Can accurately scan convert an edge of a triangle. */ struct EdgeStruct { EdgeStruct(const GradientsStruct & grad,const Vector3 * verts,int top,int bottom) { Y = WWMath::Ceil(verts[top].Y); Height = WWMath::Ceil(verts[bottom].Y) - Y; float y_prestep = Y - verts[top].Y; float real_height = verts[bottom].Y - verts[top].Y; float real_width = verts[bottom].X - verts[top].X; X = ((real_width * y_prestep)/real_height) + verts[top].X; XStep = real_width / real_height; float x_prestep = X - verts[top].X; OOZ = grad.OOZ[top] + y_prestep * grad.DOOZ_DY + x_prestep * grad.DOOZ_DX; OOZStep = XStep * grad.DOOZ_DX + grad.DOOZ_DY; } inline int Step(void) { X+=XStep; Y++; Height--; OOZ+=OOZStep; return Height; } float X; // fractional x coord float XStep; // change in x per scanline int Y; // current y coord int Height; // number of scanlines left float OOZ; // current 1/z float OOZStep; // change in 1/z per scanline }; bool IDBufferClass::Render_Triangle(const Vector3 & p0,const Vector3 & p1,const Vector3 & p2) { if ((ZBuffer == NULL) || (IDBuffer == NULL)) { return false; } int pixels_passed = 0; bool is_backfacing = Is_Backfacing(p0,p1,p2); if ((is_backfacing) && (TwoSidedRenderingEnabled == false)) { if (RenderMode == NON_OCCLUDER_MODE) { return false; } CurID = BackfaceID; } else { CurID = FrontfaceID; } /* ** Transform the coordinates to device coords ** All coordinates come in with the range -1 -> +1 */ Vector3 points[3]; points[0].X = 0.5f * (p0.X + 1.0f) * ResWidth; points[1].X = 0.5f * (p1.X + 1.0f) * ResWidth; points[2].X = 0.5f * (p2.X + 1.0f) * ResWidth; points[0].Y = 0.5f * (1.0f - p0.Y) * ResHeight; points[1].Y = 0.5f * (1.0f - p1.Y) * ResHeight; points[2].Y = 0.5f * (1.0f - p2.Y) * ResHeight; points[0].Z = 0.5f * (p0.Z + 1.001f) * 1000.0f; points[1].Z = 0.5f * (p1.Z + 1.001f) * 1000.0f; points[2].Z = 0.5f * (p2.Z + 1.001f) * 1000.0f; /* ** Sort points based on Y */ float y0 = points[0].Y; float y1 = points[1].Y; float y2 = points[2].Y; int top,middle,bottom,middle_for_compare,bottom_for_compare; if(y0 < y1) { if(y2 < y0) { top = 2; middle = 0; bottom = 1; middle_for_compare = 0; bottom_for_compare = 1; } else { top = 0; if(y1 < y2) { middle = 1; bottom = 2; middle_for_compare = 1; bottom_for_compare = 2; } else { middle = 2; bottom = 1; middle_for_compare = 2; bottom_for_compare = 1; } } } else { if(y2 < y1) { top = 2; middle = 1; bottom = 0; middle_for_compare = 1; bottom_for_compare = 0; } else { top = 1; if(y0 < y2) { middle = 0; bottom = 2; middle_for_compare = 3; bottom_for_compare = 2; } else { middle = 2; bottom = 0; middle_for_compare = 2; bottom_for_compare = 3; } } } /* ** Compute the gradients and set up the edge structures */ GradientsStruct grads(points); EdgeStruct top_to_bottom_edge(grads,points,top,bottom); EdgeStruct top_to_middle_edge(grads,points,top,middle); EdgeStruct middle_to_bottom_edge(grads,points,middle,bottom); EdgeStruct * left_edge = NULL; EdgeStruct * right_edge = NULL; bool middle_is_left = false; if (bottom_for_compare > middle_for_compare) { middle_is_left = 1 ^ is_backfacing; } else { middle_is_left = 0 ^ is_backfacing; } if (middle_is_left) { left_edge = &top_to_middle_edge; right_edge = &top_to_bottom_edge; } else { left_edge = &top_to_bottom_edge; right_edge = &top_to_middle_edge; } /* ** Fill scanlines */ int height = top_to_middle_edge.Height; while (height--) { if (RenderMode == OCCLUDER_MODE) { pixels_passed += Render_Occluder_Scanline(grads,left_edge,right_edge); } else { pixels_passed += Render_Non_Occluder_Scanline(grads,left_edge,right_edge); } left_edge->Step(); right_edge->Step(); } if (middle_is_left) { left_edge = &middle_to_bottom_edge; right_edge = &top_to_bottom_edge; } else { left_edge = &top_to_bottom_edge; right_edge = &middle_to_bottom_edge; } height = middle_to_bottom_edge.Height; while (height--) { if (RenderMode == OCCLUDER_MODE) { pixels_passed += Render_Occluder_Scanline(grads,left_edge,right_edge); } else { pixels_passed += Render_Non_Occluder_Scanline(grads,left_edge,right_edge); } left_edge->Step(); right_edge->Step(); } return (pixels_passed > 0); } int IDBufferClass::Render_Occluder_Scanline(GradientsStruct & grads,EdgeStruct * left,EdgeStruct * right) { if ((left->Y < 1) || (left->Y >= ResHeight)) { return 0; } int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceil(left->X),1.0f)); int width = WWMath::Float_To_Long(WWMath::Ceil(right->X)) - xstart; if (xstart + width > ResWidth) { width = ResWidth - xstart; } float xprestep = (float)xstart - left->X; int address = Pixel_Coords_To_Address(xstart,left->Y); float ooz = left->OOZ + xprestep * grads.DOOZ_DX; int pixel_counter = 0; /* ** Two separate loops, backfaces only render when LESS THAN */ if (CurID == BackfaceID) { while (width-- > 0) { if (ooz > ZBuffer[address]) { IDBuffer[address] = CurID; ZBuffer[address] = ooz; pixel_counter++; } ooz += grads.DOOZ_DX; address++; } /* ** Front faces render when LESS THAN OR EQUAL TO */ } else { while (width-- > 0) { if (ooz >= ZBuffer[address]) { IDBuffer[address] = CurID; ZBuffer[address] = ooz; pixel_counter++; } ooz += grads.DOOZ_DX; address++; } } PixelCounter += pixel_counter; return pixel_counter; } int IDBufferClass::Render_Non_Occluder_Scanline(GradientsStruct & grads,EdgeStruct * left,EdgeStruct * right) { if ((left->Y < 1) || (left->Y >= ResHeight)) { return 0; } int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceil(left->X),1)); int width = WWMath::Float_To_Long(WWMath::Ceil(right->X)) - xstart; if (xstart + width > ResWidth) { width = ResWidth - xstart; } float xprestep = (float)xstart - left->X; int address = Pixel_Coords_To_Address(xstart,left->Y); float ooz = left->OOZ + xprestep * grads.DOOZ_DX; while (width-- > 0) { if (ooz >= ZBuffer[address]) { PixelCounter++; return 1; } ooz += grads.DOOZ_DX; address++; } return 0; }