/*
** Command & Conquer Generals Zero Hour(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 .
*/
/* $Header: /Commando/Code/Tools/max2w3d/vxllayer.cpp 4 10/28/97 6:08p Greg_h $ */
/***********************************************************************************************
*** Confidential - Westwood Studios ***
***********************************************************************************************
* *
* Project Name : Commando / G *
* *
* File Name : VXLLAYER.CPP *
* *
* Programmer : Greg Hjelstrom *
* *
* Start Date : 06/10/97 *
* *
* Last Update : June 10, 1997 [GH] *
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* VoxelLayerClass::VoxelLayerClass -- Constructor for VoxelLayerClass *
* VoxelLayerClass::Intersect_Triangle -- Intersect a triangle with the slab *
* VoxelLayerClass::Draw_Line -- Draw a line of voxels into the slab *
* VoxelLayerClass::Scan_Triangle -- Clip and scan-convert a triangle into the slab *
* clip_tri_to_slab -- Clips a triangle against a voxel slab *
* clip_poly -- clip a polygon against a single 3D plane *
* output -- Emit a vertex into a polygons vertex list *
* inside -- Test whether a point is in the front half-space of a plane *
* intersect -- compute intersection between a line and a plane *
* clear_scan_table -- clears the static scanline table *
* fixup_scan_table -- ensure all spans are left->right in order *
* scan_edge -- Scan convert an edge *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "vxllayer.h"
#include "plane.h"
/***************************************************************************************
** local types
***************************************************************************************/
struct vertexstruct
{
Point3 Pos;
Point3 Bary;
};
struct scanstruct
{
vertexstruct P[2];
};
/***************************************************************************************
** static data
***************************************************************************************/
static scanstruct _scantab[256];
const int LEFT = 0;
const int RIGHT = 1;
const float EMPTY_SPAN = -10000.0f;
/***************************************************************************************
** local functions
***************************************************************************************/
static void clip_tri_to_slab(
Point3 p0,
Point3 p1,
Point3 p2,
float z0,
float z1,
vertexstruct * outverts,
int * setnum);
static void clip_poly(
vertexstruct * inverts,
int innum,
vertexstruct * outverts,
int * outnum,
const PlaneClass & clipplane);
static void output(
const vertexstruct & outvert,
vertexstruct * poly,
int * numverts);
static int inside(
const vertexstruct & p,
const PlaneClass & plane);
static vertexstruct intersect(
const vertexstruct & p0,
const vertexstruct & p1,
const PlaneClass & plane);
static void clear_scan_table(void);
static void fixup_scan_table(
int y0,
int y1);
static void scan_edge(
const vertexstruct & p0,
const vertexstruct & p1);
/***********************************************************************************************
* VoxelLayerClass::VoxelLayerClass -- Constructor for VoxelLayerClass *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
VoxelLayerClass::VoxelLayerClass
(
INodeListClass & object_list,
TimeValue time,
Matrix3 parenttm,
Point3 offset,
Point3 scale,
float slicez,
float sliceh,
int bmwidth,
int bmheight
)
{
unsigned i;
SliceZ = slicez;
SliceH = sliceh;
SliceZ0 = slicez - sliceh / 2;
SliceZ1 = slicez + sliceh / 2;
bitmap_width = bmwidth;
bitmap_height = bmheight;
// Initialize everything with zero
memset ( &(Solid[0][0]), 0, sizeof(Solid));
// Go through the list of objects and intersect them with the plane.
for ( i = 0; i < object_list.Num_Nodes(); i++ )
{
// Get relavent data from MAX
INode * inode = object_list[i];
Object * obj = inode->EvalWorldState(time).obj;
TriObject * tri = (TriObject *)obj->ConvertToType(time, triObjectClassID);
Mesh * mesh = &(tri->mesh);
Matrix3 objtm = inode->GetObjectTM(time);
// Compute a delta matrix which puts vertices into the parent space
Matrix3 delta = objtm * Inverse(parenttm);
// Loop through each face, intersecting it with the slice.
unsigned faces = mesh->getNumFaces();
for ( unsigned face_index = 0; face_index < faces; ++ face_index )
{
Face & face = mesh->faces [ face_index ];
// transform the vertices into the parent space
Point3 a = mesh->verts [ face.v[0] ] * delta;
Point3 b = mesh->verts [ face.v[1] ] * delta;
Point3 c = mesh->verts [ face.v[2] ] * delta;
// shift the vertices to the origin
a.x -= offset.x;
a.y -= offset.y;
b.x -= offset.x;
b.y -= offset.y;
c.x -= offset.x;
c.y -= offset.y;
// scale the vertices into the voxel grid
a.x *= scale.x;
a.y *= scale.y;
b.x *= scale.x;
b.y *= scale.y;
c.x *= scale.x;
c.y *= scale.y;
// Intersect_Triangle ( a, b, c, SliceZ );
Scan_Triangle( a, b, c );
}
}
}
/***********************************************************************************************
* VoxelLayerClass::Intersect_Triangle -- Intersect a triangle with the slab *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
void VoxelLayerClass::Intersect_Triangle
(
Point3 a,
Point3 b,
Point3 c,
float z
)
{
double start_x, start_y, end_x, end_y;
// If the triangle is wholly above or below the intersection plane,
// it does not intersect with the plane.
if ( a.z < z && b.z < z && c.z < z )
return;
if ( a.z > z && b.z > z && c.z > z )
return;
// Find the upward intersection moving counterclockwise. This will be
// the start of the edge.
if ( a.z < z && b.z >= z )
{
start_x = a.x + (b.x - a.x) * (z - a.z) / (b.z - a.z);
start_y = a.y + (b.y - a.y) * (z - a.z) / (b.z - a.z);
}
else if ( b.z < z && c.z >= z )
{
start_x = b.x + (c.x - b.x) * (z - b.z) / (c.z - b.z);
start_y = b.y + (c.y - b.y) * (z - b.z) / (c.z - b.z);
}
else if ( c.z < z && a.z >= z )
{
start_x = c.x + (a.x - c.x) * (z - c.z) / (a.z - c.z);
start_y = c.y + (a.y - c.y) * (z - c.z) / (a.z - c.z);
}
else
{
return;
}
// Find the downward intersection moving counterclockwise. This is the end
// of the edge.
if ( a.z >= z && b.z < z )
{
end_x = a.x + (b.x - a.x) * (z - a.z) / (b.z - a.z);
end_y = a.y + (b.y - a.y) * (z - a.z) / (b.z - a.z);
}
else if ( b.z >= z && c.z < z )
{
end_x = b.x + (c.x - b.x) * (z - b.z) / (c.z - b.z);
end_y = b.y + (c.y - b.y) * (z - b.z) / (c.z - b.z);
}
else if ( c.z >= z && a.z < z )
{
end_x = c.x + (a.x - c.x) * (z - c.z) / (a.z - c.z);
end_y = c.y + (a.y - c.y) * (z - c.z) / (a.z - c.z);
}
else
{
return;
}
// Draw the edge into the bitmap.
Draw_Line(start_x, start_y, end_x, end_y);
}
/***********************************************************************************************
* VoxelLayerClass::Draw_Line -- Draw a line of voxels into the slab *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
void VoxelLayerClass::Draw_Line
(
double x0,
double y0,
double x1,
double y1
)
{
// Fill in the squares containing the line's endpoints.
Add_Solid((int)x0, (int)y0);
Add_Solid((int)x1, (int)y1);
// Fill in the squares between the endpoints.
double delta_x = fabs (x1 - x0);
double delta_y = fabs (y1 - y0);
if ( delta_x > delta_y )
{
// This is an X-major line.
if ( x0 > x1 )
{
double temp = x0;
x0 = x1;
x1 = temp;
temp = y0;
y0 = y1;
y1 = temp;
}
double step_y = (y1 - y0) / delta_x;
double y = y0 + step_y * (floor (x0 + 1) - x0);
for ( int x = (int) x0; x < (int) x1; ++ x )
{
if ( (int) y >= 0 && (int) y < bitmap_height )
{
Add_Solid(x, (int)y);
Add_Solid(x + 1, (int)y);
}
y += step_y;
}
}
else
{
// This is a Y-major line.
if ( y0 > y1 )
{
double temp = x0;
x0 = x1;
x1 = temp;
temp = y0;
y0 = y1;
y1 = temp;
}
double step_x = (x1 - x0) / delta_y;
double x = x0 + step_x * (floor (y0 + 1) - y0);
for ( int y = (int) y0; y < (int) y1; ++ y )
{
if ( (int) x >= 0 && (int) x < 256 )
{
Add_Solid((int)x, y);
Add_Solid((int)x, y+1);
}
}
}
}
/***********************************************************************************************
* VoxelLayerClass::Scan_Triangle -- Clip and scan-convert a triangle into the slab *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
void VoxelLayerClass::Scan_Triangle
(
Point3 p0,
Point3 p1,
Point3 p2
)
{
int i;
// check if the entire triangle is above or below the slab:
if (p0.z < SliceZ0 && p1.z < SliceZ0 && p2.z < SliceZ1) return;
if (p0.z > SliceZ1 && p1.z > SliceZ1 && p2.z > SliceZ1) return;
// clip the triangle to the slab
vertexstruct polyvert[8];
int numverts;
clip_tri_to_slab(p0,p1,p2,SliceZ0,SliceZ1,polyvert,&numverts);
if (numverts == 0) return;
// clear the scanline table, get y-extents of polygon
clear_scan_table();
float miny = polyvert[0].Pos.y;
float maxy = polyvert[0].Pos.y;
for (i=1; i maxy) maxy = polyvert[i].Pos.y;
}
// scanconvert the triangle
int start = numverts - 1;
for (i=0; i= 0.0f) {
return 1;
} else {
return 0;
}
}
/***********************************************************************************************
* intersect -- compute intersection between a line and a plane *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
static vertexstruct intersect
(
const vertexstruct & p0,
const vertexstruct & p1,
const PlaneClass & plane
)
{
float t;
Point3 delta = p1.Pos - p0.Pos;
float num = -( plane.N[0] * p0.Pos.x +
plane.N[1] * p0.Pos.y +
plane.N[2] * p0.Pos.z + plane.D );
float den = plane.N[0] * delta.x +
plane.N[1] * delta.y +
plane.N[2] * delta.z;
if (den != 0.0f) {
t = num / den;
} else {
t = 0.0f;
}
vertexstruct i;
i.Pos = (1.0f - t) * p0.Pos + t*p1.Pos;
i.Bary = (1.0f - t) * p0.Bary + t*p1.Bary;
return i;
}
/***********************************************************************************************
* clear_scan_table -- clears the static scanline table *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
static void clear_scan_table(void)
{
memset(_scantab,0,sizeof(_scantab));
for (int i=0; i<256; i++) {
_scantab[i].P[0].Pos.x = EMPTY_SPAN;
_scantab[i].P[1].Pos.x = EMPTY_SPAN;
}
}
/***********************************************************************************************
* fixup_scan_table -- ensure all spans are left->right in order *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
static void fixup_scan_table(int y0,int y1)
{
int i;
assert(y1 >= y0);
// Ensure the left -> right convention is followed.
for (i=y0; i<=y1; i++) {
if (_scantab[i].P[LEFT].Pos.x > _scantab[i].P[RIGHT].Pos.x) {
vertexstruct tmp = _scantab[i].P[LEFT];
_scantab[i].P[LEFT] = _scantab[i].P[RIGHT];
_scantab[i].P[RIGHT] = tmp;
}
}
// Ensure that we leave no gaps.
for (i=y0; i _scantab[i].P[RIGHT].Pos.x) {
_scantab[i+1].P[LEFT].Pos.x = _scantab[i].P[RIGHT].Pos.x;
}
}
}
/***********************************************************************************************
* scan_edge -- Scan convert an edge *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/10/1997 GH : Created. *
*=============================================================================================*/
static void scan_edge
(
const vertexstruct & p0,
const vertexstruct & p1
)
{
// is this a perfectly horizontal edge:
if (floor(p0.Pos.y) == floor(p1.Pos.y)) {
int si = (int)floor(p0.Pos.y);
const vertexstruct *left, *right;
if (p0.Pos.x < p1.Pos.x) {
left = &p0;
right = &p1;
} else {
left = &p1;
right = &p0;
}
// does this scanline already have a span in it?
if (_scantab[si].P[0].Pos.x != EMPTY_SPAN) {
// yes, expand this scanline's span to include this span
if (left->Pos.x < _scantab[si].P[LEFT].Pos.x) {
_scantab[si].P[LEFT] = *left;
}
if (right->Pos.x > _scantab[si].P[RIGHT].Pos.x) {
_scantab[si].P[RIGHT] = *right;
}
} else {
// no, set this scanline with the span for this edge
_scantab[si].P[LEFT] = *left;
_scantab[si].P[RIGHT] = *right;
}
return;
}
// is this a left or right edge:
int side;
const vertexstruct *top, *bot;
if (p0.Pos.y < p1.Pos.y) {
side = RIGHT;
top = &p0;
bot = &p1;
} else {
side = LEFT;
top = &p1;
bot = &p0;
}
// scan the edge into _scantab
for (double y = floor(top->Pos.y); y <= floor(bot->Pos.y); y += 1.0f) {
// parametric position on the scanline:
double t = (y - floor(top->Pos.y)) / (floor(bot->Pos.y) - floor(top->Pos.y));
// position:
_scantab[(int)y].P[side].Pos = (1.0f - (float)t)*top->Pos + (float)t*bot->Pos;
// barycentric coords:
_scantab[(int)y].P[side].Bary = (1.0f - (float)t)*top->Bary + (float)t*bot->Bary;
}
}