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/wwphys/lightsolve.cpp

608 lines
18 KiB
C++
Raw Permalink Normal View History

/*
** 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 : wwphys *
* *
* $Archive:: /Commando/Code/wwphys/lightsolve.cpp $*
* *
* Original Author:: Greg Hjelstrom *
* *
* $Author:: Patrick $*
* *
* $Modtime:: 3/28/02 11:53a $*
* *
* $Revision:: 3 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "lightsolve.h"
#include "phys.h"
#include "staticphys.h"
#include "rendobj.h"
#include "mesh.h"
#include "dx8renderer.h"
#include "simplevec.h"
#include "vp.h"
#include "lightphys.h"
#include "light.h"
#include "lightsolvecontext.h"
#include "lightsolveprogress.h"
#include "renegadeterrainpatch.h"
/**
** VertexSolveClass
** This class does the job of generated a vertex solve for a mesh.
*/
class VertexSolveClass
{
public:
VertexSolveClass(void) {}
void Light_Mesh(LightSolveContextClass & context,MeshClass * mesh, NonRefPhysListClass & lights);
void Light_Terrain(LightSolveContextClass & context,RenegadeTerrainPatchClass * patch, NonRefPhysListClass & lights);
protected:
void Grow_Arrays(int new_vcount);
void Add_Light_To_Vertex(LightSolveContextClass & context,int idx,LightClass * light_obj);
SimpleVecClass<Vector3> Position;
SimpleVecClass<Vector3> Normal;
SimpleVecClass<Vector4> MeshAmbient;
SimpleVecClass<Vector4> MeshDiffuse;
SimpleVecClass<Vector4> AmbientSolve;
SimpleVecClass<Vector4> DiffuseSolve;
SimpleVecClass<Vector4> Solve;
};
static VertexSolveClass TheVertexSolver;
void VertexSolveClass::Light_Mesh(LightSolveContextClass & context,MeshClass * mesh, NonRefPhysListClass & lights)
{
int vi;
context.Get_Progress().Set_Current_Mesh_Name(mesh->Get_Name());
context.Get_Progress().Set_Current_Mesh_Vertex_Count(mesh->Peek_Model()->Get_Vertex_Count());
/*
** Get the model and initialize our arrays
*/
MeshModelClass * model = mesh->Peek_Model();
int vcount = model->Get_Vertex_Count();
Grow_Arrays(vcount);
/*
** Transform the positions and normals into world space
*/
Matrix3D tm = mesh->Get_Transform();
const Vector3 * src_verts = model->Get_Vertex_Array();
const Vector3 * src_norms = model->Get_Vertex_Normal_Array();
VectorProcessorClass::Transform(&(Position[0]), src_verts, tm, vcount);
tm.Set_Translation(Vector3(0,0,0));
VectorProcessorClass::Transform(&(Normal[0]), src_norms, tm, vcount);
/*
** Fill the mesh ambient and diffuse arrays with the per vertex material
** color (either from the material itself or the vertex color array)
**
** LIGHT SOLVE GENERATION RULES:
** - for each vertex
** - for ambient and diffuse
** - if this vertex is supposed to use a color array in any pass copy the color from the array
** - else copy the material setting from pass0
**
** LIGHT SOLVE INSTALL RULES:
** - for each pass
** - for each vertex material
** - if this material has any emissive component, leave the vmtl alone
** - else, point the emissive source to the new light solve color array,
** make the diffuse and ambient use the material settings.
*/
unsigned * dcg = NULL;
for (int pi=0; pi<model->Get_Pass_Count(); pi++) {
if (model->Get_DCG_Array(pi) != NULL) {
dcg = model->Get_DCG_Array(pi);
}
}
for (vi=0; vi<model->Get_Vertex_Count(); vi++) {
bool use_array = false;
for (int pi=0; pi<model->Get_Pass_Count(); pi++) {
VertexMaterialClass * pass_mtl = model->Peek_Material(vi,pi);
if ( (pass_mtl != NULL) &&
(dcg != NULL) &&
(pass_mtl->Get_Diffuse_Color_Source() == VertexMaterialClass::COLOR1) )
{
use_array = true;
}
}
/*
** Set up the ambient color for this vertex
*/
VertexMaterialClass * vmtl = model->Peek_Material(vi,0);
if (use_array) {
MeshAmbient[vi] = DX8Wrapper::Convert_Color(dcg[vi]);
} else if (vmtl != NULL) {
Vector3 ambient;
vmtl->Get_Ambient(&ambient);
MeshAmbient[vi] = Vector4(ambient.X,ambient.Y,ambient.Z,1.0f);
} else {
MeshAmbient[vi].Set(1,1,1,1);
}
/*
** Set up the diffuse color for this vertex
*/
if (use_array) {
MeshDiffuse[vi] = DX8Wrapper::Convert_Color(dcg[vi]);
} else if (vmtl != NULL) {
Vector3 diffuse;
vmtl->Get_Diffuse(&diffuse);
MeshDiffuse[vi] = Vector4(diffuse.X,diffuse.Y,diffuse.Z,1.0f);
} else {
MeshDiffuse[vi].Set(1,1,1,1);
}
}
/*
** Compute the light solve
*/
LightClass * sun = PhysicsSceneClass::Get_Instance()->Get_Sun_Light();
Vector3 scene_ambient = PhysicsSceneClass::Get_Instance()->Get_Ambient_Light();
int callback_counter = 0;
for (vi=0; vi<model->Get_Vertex_Count(); vi++) {
AmbientSolve[vi].X = scene_ambient.X;
AmbientSolve[vi].Y = scene_ambient.Y;
AmbientSolve[vi].Z = scene_ambient.Z;
AmbientSolve[vi].W = 1.0f;
DiffuseSolve[vi].X = 0.0f;
DiffuseSolve[vi].Y = 0.0f;
DiffuseSolve[vi].Z = 0.0f;
DiffuseSolve[vi].W = MeshDiffuse[vi].W;
/*
** Sun
*/
Add_Light_To_Vertex(context,vi,sun);
/*
** Other lights
*/
NonRefPhysListIterator it(&lights);
while (!it.Is_Done()) {
LightPhysClass * light = it.Peek_Obj()->As_LightPhysClass();
if ((light) && (light->Peek_Model()) && (light->Peek_Model()->Class_ID() == RenderObjClass::CLASSID_LIGHT)) {
LightClass * light_obj = (LightClass*)light->Peek_Model();
Add_Light_To_Vertex(context,vi,light_obj);
}
it.Next();
}
context.Get_Progress().Set_Current_Vertex(vi);
callback_counter++;
if (callback_counter > 100) {
callback_counter = 0;
context.Update_Observer();
}
}
VectorProcessorClass::Clamp(&(AmbientSolve[0]),&(AmbientSolve[0]), 0.0f, 1.0f,vcount);
VectorProcessorClass::Clamp(&(DiffuseSolve[0]),&(DiffuseSolve[0]), 0.0f, 1.0f,vcount);
/*
** Modulate the accumulated light by the material properties
*/
for (vi=0; vi<vcount; vi++) {
Solve[vi].X = AmbientSolve[vi].X * MeshAmbient[vi].X + DiffuseSolve[vi].X * MeshDiffuse[vi].X;
Solve[vi].Y = AmbientSolve[vi].Y * MeshAmbient[vi].Y + DiffuseSolve[vi].Y * MeshDiffuse[vi].Y;
Solve[vi].Z = AmbientSolve[vi].Z * MeshAmbient[vi].Z + DiffuseSolve[vi].Z * MeshDiffuse[vi].Z;
Solve[vi].W = DiffuseSolve[vi].W;
}
VectorProcessorClass::Clamp(&(Solve[0]),&(Solve[0]), 0.0f, 1.0f,vcount);
mesh->Install_User_Lighting_Array(&(Solve[0]));
context.Update_Observer();
REF_PTR_RELEASE(sun);
}
static Vector3 _offset (0, 0, 0);
void VertexSolveClass::Light_Terrain(LightSolveContextClass & context,RenegadeTerrainPatchClass * patch, NonRefPhysListClass & lights)
{
int vi;
context.Get_Progress().Set_Current_Mesh_Name(patch->Get_Name());
context.Get_Progress().Set_Current_Mesh_Vertex_Count(patch->Get_Vertex_Count());
/*
** Get the model and initialize our arrays
*/
int vcount = patch->Get_Vertex_Count();
Grow_Arrays(vcount);
/*
** Transform the positions and normals into world space
*/
Matrix3D tm = patch->Get_Transform();
const Vector3 * src_verts = patch->Get_Vertex_Array();
const Vector3 * src_norms = patch->Get_Vertex_Normal_Array();
VectorProcessorClass::Transform(&(Position[0]), src_verts, tm, vcount);
tm.Set_Translation(Vector3(0,0,0));
VectorProcessorClass::Transform(&(Normal[0]), src_norms, tm, vcount);
/*
** For heightfield terrains, the mesh ambient and diffuse colors are white
*/
for (int index = 0; index < vcount; index ++) {
MeshAmbient[index].Set (1.0F, 1.0F, 1.0F, 1.0F);
MeshDiffuse[index].Set (1.0F, 1.0F, 1.0F, 1.0F);
}
/*
** Compute the light solve
*/
LightClass * sun = PhysicsSceneClass::Get_Instance()->Get_Sun_Light();
Vector3 scene_ambient = PhysicsSceneClass::Get_Instance()->Get_Ambient_Light();
int callback_counter = 0;
for (vi = 0; vi < vcount; vi ++) {
AmbientSolve[vi].X = scene_ambient.X;
AmbientSolve[vi].Y = scene_ambient.Y;
AmbientSolve[vi].Z = scene_ambient.Z;
AmbientSolve[vi].W = 1.0f;
DiffuseSolve[vi].X = 0.0f;
DiffuseSolve[vi].Y = 0.0f;
DiffuseSolve[vi].Z = 0.0f;
DiffuseSolve[vi].W = MeshDiffuse[vi].W;
/*
** Sun
*/
Add_Light_To_Vertex(context,vi,sun);
/*
** Other lights
*/
NonRefPhysListIterator it(&lights);
while (!it.Is_Done()) {
LightPhysClass * light = it.Peek_Obj()->As_LightPhysClass();
if ((light) && (light->Peek_Model()) && (light->Peek_Model()->Class_ID() == RenderObjClass::CLASSID_LIGHT)) {
LightClass * light_obj = (LightClass*)light->Peek_Model();
Add_Light_To_Vertex(context,vi,light_obj);
}
it.Next();
}
context.Get_Progress().Set_Current_Vertex(vi);
callback_counter++;
if (callback_counter > 100) {
callback_counter = 0;
context.Update_Observer();
}
}
VectorProcessorClass::Clamp(&(AmbientSolve[0]),&(AmbientSolve[0]), 0.0f, 1.0f,vcount);
VectorProcessorClass::Clamp(&(DiffuseSolve[0]),&(DiffuseSolve[0]), 0.0f, 1.0f,vcount);
/*
** Modulate the accumulated light by the material properties
*/
for (vi=0; vi<vcount; vi++) {
Solve[vi].X = AmbientSolve[vi].X * MeshAmbient[vi].X + DiffuseSolve[vi].X * MeshDiffuse[vi].X;
Solve[vi].Y = AmbientSolve[vi].Y * MeshAmbient[vi].Y + DiffuseSolve[vi].Y * MeshDiffuse[vi].Y;
Solve[vi].Z = AmbientSolve[vi].Z * MeshAmbient[vi].Z + DiffuseSolve[vi].Z * MeshDiffuse[vi].Z;
Solve[vi].W = DiffuseSolve[vi].W;
}
VectorProcessorClass::Clamp(&(Solve[0]),&(Solve[0]), 0.0f, 1.0f,vcount);
/*
** Pass the resultant vertex colors onto the patch
*/
for (vi=0; vi<vcount; vi++) {
patch->Set_Vertex_Color (vi, Vector3 (Solve[vi].X, Solve[vi].Y, Solve[vi].Z));
}
//
// Let the patch know that it is now prelit
//
patch->Set_Is_Prelit (true);
return ;
}
void VertexSolveClass::Add_Light_To_Vertex(LightSolveContextClass & context,int vi,LightClass * light_obj)
{
if (light_obj->Is_Within_Attenuation_Radius(Position[vi])) {
// cast ray...
bool is_occluded = false;
#pragma message("need a collision group for light occlusion here...")
if (context.Is_Occlusion_Enabled()) {
CastResultStruct res;
Vector3 p0 = Position[vi];
Vector3 p1 = light_obj->Get_Position();
if (light_obj->Get_Type() == LightClass::DIRECTIONAL) {
Vector3 dir = -(light_obj->Get_Transform().Get_Z_Vector());
p1 = p0 + dir * light_obj->Get_Attenuation_Range();
}
const float MOVE_AMOUNT = 0.25f; // can't be occluded closer than this
Vector3 delta = p1-p0;
delta.Normalize();
p0 += MOVE_AMOUNT * delta;
p0 += _offset;
p1 += _offset;
LineSegClass ray(p0,p1);
PhysRayCollisionTestClass raytest(ray,&res,0,COLLISION_TYPE_PROJECTILE);
raytest.CheckDynamicObjs = false;
PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);
if (res.Fraction < 1.0f) {
is_occluded = true;
}
}
if (!is_occluded) {
Vector3 ambient;
Vector3 diffuse;
light_obj->Compute_Lighting(Position[vi],Normal[vi],&ambient,&diffuse);
AmbientSolve[vi] += Vector4(ambient.X,ambient.Y,ambient.Z,0.0f);
DiffuseSolve[vi] += Vector4(diffuse.X,diffuse.Y,diffuse.Z,0.0f);
}
}
}
void VertexSolveClass::Grow_Arrays(int vcount)
{
Position.Uninitialised_Grow(vcount);
Normal.Uninitialised_Grow(vcount);
AmbientSolve.Uninitialised_Grow(vcount);
DiffuseSolve.Uninitialised_Grow(vcount);
Solve.Uninitialised_Grow(vcount);
MeshAmbient.Uninitialised_Grow(vcount);
MeshDiffuse.Uninitialised_Grow(vcount);
}
/*****************************************************************************
**
** LightSolveClass Implementation
**
*****************************************************************************/
/*
Global Solve:
- start with StaticObjList
- pass list to cull function
- cull function gens new list with objects that will not be lit removed
- pass to solve function which accepts a list
- iterate list, pass each to solve function for an object
List Solve:
- pass list to cull function
- cull function gens new list with objects that will not be lit removed
- pass to solve function which accepts a list
- iterate list, pass each to solve function for an object
*/
void LightSolveClass::Generate_Static_Light_Solve(LightSolveContextClass & context)
{
RefPhysListIterator it = PhysicsSceneClass::Get_Instance()->Get_Static_Object_Iterator();
/*
** Build a list of the objects to be solved
*/
RefPhysListClass solve_list;
while (!it.Is_Done()) {
StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
if ((obj != NULL) && (Does_Obj_Get_Static_Light_Solve(obj))) {
solve_list.Add(obj);
}
it.Next();
}
/*
** Pass the list to the solve function
*/
Compute_Solve(context,solve_list);
}
void LightSolveClass::Generate_Static_Light_Solve(LightSolveContextClass & context,RefPhysListClass & input_list)
{
RefPhysListClass solve_list;
/*
** Cull out objects which should not get a lighting solve
*/
RefPhysListIterator it(&input_list);
while (!it.Is_Done()) {
StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
if ((obj != NULL) && (Does_Obj_Get_Static_Light_Solve(obj))) {
solve_list.Add(obj);
}
it.Next();
}
/*
** Pass the list to the solve function
*/
Compute_Solve(context,solve_list);
}
void LightSolveClass::Compute_Solve(LightSolveContextClass & context,RefPhysListClass & obj_list)
{
RefPhysListIterator it(&obj_list);
/*
** Count the objects for the progress display
*/
int count = 0;
while (!it.Is_Done()) {
count++;
it.Next();
}
context.Get_Progress().Set_Object_Count(count);
/*
** Generate a light solve for each static object
*/
it.First();
while (!it.Is_Done() && !context.Get_Progress().Is_Cancel_Requested()) {
StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
if (obj != NULL) {
Compute_Solve(context,obj);
context.Get_Progress().Increment_Processed_Object_Count();
}
it.Next();
}
/*
** Re-gen all VB's, IB's, etc
*/
TheDX8MeshRenderer.Invalidate();
}
void LightSolveClass::Compute_Solve(LightSolveContextClass & context,StaticPhysClass * obj)
{
WWASSERT(obj != NULL);
if (context.Get_Progress().Get_Object_Count() == 0) {
context.Get_Progress().Set_Object_Count(1);
}
/*
** Generate a light solve for each mesh in this object
*/
if (obj->Is_Model_Pre_Lit() == false) {
NonRefPhysListClass light_list;
PhysicsSceneClass::Get_Instance()->Collect_Lights(obj->Get_Cull_Box(),true,false,&light_list);
Compute_Solve(context,obj->Peek_Model(),light_list);
}
}
void LightSolveClass::Compute_Solve(LightSolveContextClass & context,RenderObjClass * obj,NonRefPhysListClass & light_list)
{
/*
** Mark this render object as containing a static lighting solve
*/
obj->Set_Has_User_Lighting(true);
/*
** Recurse through all sub-objects
*/
WWASSERT(obj != NULL);
for (int i=0; i<obj->Get_Num_Sub_Objects(); i++) {
RenderObjClass * sub_obj = obj->Get_Sub_Object(i);
if (sub_obj != NULL) {
Compute_Solve(context,sub_obj,light_list);
REF_PTR_RELEASE(sub_obj);
}
}
/*
** For all mesh objects, generate a vertex lighting solution.
*/
if (obj->Class_ID() == RenderObjClass::CLASSID_MESH) {
MeshClass * mesh = (MeshClass*)obj;
WWDEBUG_SAY(("Generating Solve for Mesh: %s\r\n",mesh->Get_Name()));
TheVertexSolver.Light_Mesh(context,mesh,light_list);
} else if (obj->Class_ID() == RenderObjClass::CLASSID_RENEGADE_TERRAIN) {
TheVertexSolver.Light_Terrain(context,(RenegadeTerrainPatchClass *)obj,light_list);
}
}
bool LightSolveClass::Does_Obj_Get_Static_Light_Solve(StaticPhysClass * obj)
{
if (obj->Is_Model_Pre_Lit()) {
return false;
}
return Does_Model_Get_Static_Light_Solve(obj->Peek_Model());
}
bool LightSolveClass::Does_Model_Get_Static_Light_Solve(RenderObjClass * model)
{
if (model == NULL) {
return false;
}
if (!model->Is_Not_Hidden_At_All()) {
return false;
}
if (model->Get_Num_Sub_Objects() > 0) {
bool any_sub_objs_visible = false;
for (int i=0; i<model->Get_Num_Sub_Objects(); i++) {
RenderObjClass * sub_obj = model->Get_Sub_Object(i);
if (sub_obj != NULL) {
if (sub_obj->Is_Not_Hidden_At_All()) {
any_sub_objs_visible = true;
}
REF_PTR_RELEASE(sub_obj);
}
}
if (any_sub_objs_visible == false) {
return false;
}
}
// TODO: check if the entire model is emissive???
return true;
}