This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/ww3d2/visrasterizer.cpp

726 lines
18 KiB
C++

/*
** 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 <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** 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<Vector3> 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; j++) {
cur_point_in_front = !plane.In_Front(Verts[i]); // Note, plane nomral is out so we invert this test
if (prev_point_in_front) {
if (cur_point_in_front) {
// Previous vertex was in front of plane and this vertex is in
// front of the plane so we emit this vertex.
dest.Add_Vertex(Verts[i]);
} else {
// Previous vert was in front, this vert is behind, compute
// the intersection and emit the point.
plane.Compute_Intersection(Verts[iprev],Verts[i],&alpha);
Lerp(Verts[iprev],Verts[i],alpha,&int_point);
dest.Add_Vertex(int_point);
}
} else {
if (cur_point_in_front) {
// segment is going from the back halfspace to the front halfspace
// compute the intersection and emit it, then continue
// the edge into the front halfspace and emit the end point.
plane.Compute_Intersection(Verts[iprev],Verts[i],&alpha);
Lerp(Verts[iprev],Verts[i],alpha,&int_point);
dest.Add_Vertex(int_point);
dest.Add_Vertex(Verts[i]);
}
}
prev_point_in_front = cur_point_in_front;
iprev = i;
//i = (i+1)%(Verts.Count());
i++;
if (i>=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; i<vcount; i++) {
Camera->Project_Camera_Space_Point(tverts[i],tverts[i]);
}
/*
** 2. Pass triangles on to the ID buffer for scan conversion
*/
for (int tri_index=0; tri_index<tcount; tri_index++) {
const TriIndex & tri = tris[tri_index];
pixel_passed |= IDBuffer.Render_Triangle(tverts[tri.I],tverts[tri.J],tverts[tri.K]);
if (pixel_passed && (IDBuffer.Get_Render_Mode() == IDBufferClass::NON_OCCLUDER_MODE)) {
return true;
}
}
return pixel_passed;
}
bool VisRasterizerClass::Render_Triangles_Clip
(
const Vector3 * verts,
int vcount,
const TriIndex * tris,
int tcount
)
{
bool pixel_passed = false;
/*
** 1. transform triangles into homogeneous view space
*/
const Matrix3D & tm = Get_MV_Transform();
Vector3 * tverts = Get_Temp_Vertex_Buffer(vcount);
VectorProcessorClass::Transform(tverts,verts,tm,vcount);
/*
** 2. get the frustum clipping planes
*/
const PlaneClass *planes = Camera->Get_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<tcount; tri_index++) {
/*
** Copy triangle data into the vis clipping structure
*/
_VisPoly0.Reset();
_VisPoly0.Add_Vertex(tverts[tris[tri_index].I]);
_VisPoly0.Add_Vertex(tverts[tris[tri_index].J]);
_VisPoly0.Add_Vertex(tverts[tris[tri_index].K]);
/*
** Clip against the view frustum
*/
_VisPoly0.Clip(planes[0],_VisPoly1);
_VisPoly1.Clip(planes[1],_VisPoly0);
_VisPoly0.Clip(planes[2],_VisPoly1);
_VisPoly1.Clip(planes[3],_VisPoly0);
_VisPoly0.Clip(planes[4],_VisPoly1);
_VisPoly1.Clip(planes[5],_VisPoly0);
/*
** Project the vertices
*/
int final_vcount = _VisPoly0.Verts.Count();
if (final_vcount >= 3) {
Vector3 * final_verts = &(_VisPoly0.Verts[0]);
int i;
for (i=0; i<final_vcount; i++) {
Camera->Project_Camera_Space_Point(final_verts[i],final_verts[i]);
}
/*
** Pass the resulting triangle fan to the IDBuffer
*/
for (i=1; i<final_vcount - 1; i++) {
pixel_passed |= IDBuffer.Render_Triangle(final_verts[0],final_verts[i],final_verts[i+1]);
if (pixel_passed && (IDBuffer.Get_Render_Mode() == IDBufferClass::NON_OCCLUDER_MODE)) {
return true;
}
}
}
}
return pixel_passed;
}
/*********************************************************************************************
IDBufferClass Implementation
*********************************************************************************************/
IDBufferClass::IDBufferClass(void) :
BackfaceID(0),
FrontfaceID(0),
CurID(0),
RenderMode(OCCLUDER_MODE),
TwoSidedRenderingEnabled(false),
PixelCounter(0),
ResWidth(0),
ResHeight(0),
IDBuffer(NULL),
ZBuffer(NULL)
{
}
IDBufferClass::~IDBufferClass(void)
{
Reset();
}
void IDBufferClass::Set_Resolution(int w,int h)
{
if ((w == ResWidth) && (h == ResHeight)) {
return;
} else {
ResWidth = w;
ResHeight = h;
Allocate_Buffers();
}
}
void IDBufferClass::Get_Resolution(int * get_w,int * get_h)
{
if (get_w != NULL) { *get_w = ResWidth; }
if (get_h != NULL) { *get_h = ResHeight; }
}
void IDBufferClass::Reset(void)
{
if (IDBuffer!=NULL) {
delete[] IDBuffer;
IDBuffer = NULL;
}
if (ZBuffer != NULL) {
delete[] ZBuffer;
ZBuffer = NULL;
}
PixelCounter = 0;
}
void IDBufferClass::Allocate_Buffers(void)
{
Reset();
int bufsize = ResWidth * ResHeight;
if (bufsize > 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;
}