You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
16 KiB
485 lines
16 KiB
#include "common.h"
|
|
#include "lighting.h"
|
|
|
|
unsigned char sample_lightmap(const world_t* world, const face_t* face, const BoundBox& bounds, const Vec3& point)
|
|
{
|
|
if (face->lightmap < 0)
|
|
return 0;
|
|
|
|
const unsigned char* lightmap = &world->lightmap[face->lightmap];
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
|
|
int width, height;
|
|
float u, v;
|
|
switch (plane->type)
|
|
{
|
|
case 0:
|
|
case 3:
|
|
// Towards X
|
|
width = (int)(ceil(bounds.max.y / 16) - floor(bounds.min.y / 16)) * 16;
|
|
height = (int)(ceil(bounds.max.z / 16) - floor(bounds.min.z / 16)) * 16;
|
|
u = (point.y - bounds.min.y) / (bounds.max.y - bounds.min.y);
|
|
v = (point.z - bounds.min.z) / (bounds.max.z - bounds.min.z);
|
|
break;
|
|
case 1:
|
|
case 4:
|
|
// Towards Y
|
|
width = (int)(ceil(bounds.max.x / 16) - floor(bounds.min.x / 16)) * 16;
|
|
height = (int)(ceil(bounds.max.z / 16) - floor(bounds.min.z / 16)) * 16;
|
|
u = (point.x - bounds.min.x) / (bounds.max.x - bounds.min.x);
|
|
v = (point.z - bounds.min.z) / (bounds.max.z - bounds.min.z);
|
|
break;
|
|
case 2:
|
|
case 5:
|
|
// Towards Z
|
|
width = (int)(ceil(bounds.max.x / 16) - floor(bounds.min.x / 16)) * 16;
|
|
height = (int)(ceil(bounds.max.y / 16) - floor(bounds.min.y / 16)) * 16;
|
|
u = (point.x - bounds.min.x) / (bounds.max.x - bounds.min.x);
|
|
v = (point.y - bounds.min.y) / (bounds.max.y - bounds.min.y);
|
|
break;
|
|
default:
|
|
printf("Error: unknown plane type %d\n", plane->type);
|
|
return 0;
|
|
}
|
|
|
|
height >>= 4;
|
|
width >>= 4;
|
|
|
|
return lightmap[(int)(v * (width + 1) + u)];
|
|
}
|
|
|
|
void export_lightmap(const world_t* world, const face_t* face, const BoundBox& bounds, int faceIdx)
|
|
{
|
|
if (face->lightmap < 0)
|
|
return;
|
|
|
|
const unsigned char* lightmap = &world->lightmap[face->lightmap];
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
|
|
int width, height;
|
|
switch (plane->type)
|
|
{
|
|
case 0:
|
|
case 3:
|
|
// Towards X
|
|
width = (int)(ceil(bounds.max.y / 16) - floor(bounds.min.y / 16));
|
|
height = (int)(ceil(bounds.max.z / 16) - floor(bounds.min.z / 16));
|
|
break;
|
|
case 1:
|
|
case 4:
|
|
// Towards Y
|
|
width = (int)(ceil(bounds.max.x / 16) - floor(bounds.min.x / 16));
|
|
height = (int)(ceil(bounds.max.z / 16) - floor(bounds.min.z / 16));
|
|
break;
|
|
case 2:
|
|
case 5:
|
|
// Towards Z
|
|
width = (int)(ceil(bounds.max.x / 16) - floor(bounds.min.x / 16));
|
|
height = (int)(ceil(bounds.max.y / 16) - floor(bounds.min.y / 16));
|
|
break;
|
|
default:
|
|
printf("Error: unknown plane type %d\n", plane->type);
|
|
return;
|
|
}
|
|
|
|
width += 1;
|
|
|
|
char path[_MAX_PATH];
|
|
sprintf_s(path, _MAX_PATH, "lightmap_face%d_e%d_PT%d_%dx%d.raw", faceIdx, face->ledge_num, plane->type, width, height);
|
|
FILE* flm;
|
|
fopen_s(&flm, path, "wb");
|
|
if (!flm)
|
|
return;
|
|
|
|
for (int y = 0; y < height; ++y)
|
|
{
|
|
fwrite(&lightmap[y * width], sizeof(unsigned char), width, flm);
|
|
}
|
|
|
|
fclose(flm);
|
|
}
|
|
|
|
std::unordered_map<const edge_t*, EdgeData> analyze_edges(const world_t* world)
|
|
{
|
|
std::unordered_map<const edge_t*, EdgeData> edgeData;
|
|
|
|
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
|
|
{
|
|
const face_t* face = &world->faces[faceIdx];
|
|
const int* edgeList = &world->edgeList[face->ledge_id];
|
|
for (int i = 0; i < face->ledge_num; ++i, ++edgeList)
|
|
{
|
|
int edgeIdx = *edgeList;
|
|
if (edgeIdx < 0)
|
|
edgeIdx = -edgeIdx; // Reverse direction edge
|
|
|
|
const edge_t* edge = &world->edges[edgeIdx];
|
|
|
|
auto iter = edgeData.find(edge);
|
|
if (iter != edgeData.end())
|
|
{
|
|
iter->second.faces.push_back(face);
|
|
}
|
|
else
|
|
{
|
|
EdgeData newData = { 0 };
|
|
newData.edgeIndex = edgeIdx;
|
|
newData.faces.push_back(face);
|
|
edgeData[edge] = newData;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto iter = edgeData.begin(); iter != edgeData.end(); ++iter)
|
|
{
|
|
size_t numFaces = iter->second.faces.size();
|
|
switch (numFaces)
|
|
{
|
|
case 1:
|
|
iter->second.isSharpEdge = true;
|
|
break;
|
|
case 2:
|
|
{
|
|
// TODO: take into account the face's side
|
|
auto faceA = iter->second.faces[0];
|
|
auto faceB = iter->second.faces[1];
|
|
const plane_t* planeA = &world->planes[faceA->plane_id];
|
|
const plane_t* planeB = &world->planes[faceB->plane_id];
|
|
vec3_t normalA = faceA->side ? -planeA->normal : planeA->normal;
|
|
vec3_t normalB = faceB->side ? -planeB->normal : planeB->normal;
|
|
float dot = normalA.dotProduct(normalB);
|
|
bool isSmooth = dot >= 0.5f;//&& dot <= 1;
|
|
iter->second.isSharpEdge = !isSmooth;
|
|
break;
|
|
}
|
|
default:
|
|
printf("Edge at index %d has %d adjacent face(s), weird\n", iter->second.edgeIndex, numFaces);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return edgeData;
|
|
}
|
|
|
|
unsigned char compute_faceVertex_light(const world_t* world, const face_t* face, unsigned short vertexIndex, const FaceBounds& faceBounds, const std::unordered_map<const edge_t*, EdgeData>& edgeData)
|
|
{
|
|
const vertex_t* vertex = &world->vertices[vertexIndex];
|
|
auto point = vertex->toVec();
|
|
|
|
// Sample this face's lighting contribution
|
|
unsigned int light = sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight);
|
|
int numSamples = 1;
|
|
|
|
// Collect edges connected to this vertex, filter out the smooth ones only
|
|
std::vector<const edge_t*> smoothEdges;
|
|
for (auto iter = edgeData.begin(); iter != edgeData.end(); ++iter)
|
|
{
|
|
auto edge = iter->first;
|
|
if (edge->vertex0 != vertexIndex && edge->vertex1 != vertexIndex)
|
|
continue;
|
|
|
|
if (iter->second.isSharpEdge)
|
|
continue;
|
|
|
|
// If the current face doesn't appear in this edge's adjacency list, we're not interested
|
|
for (auto faceIter = iter->second.faces.begin(); faceIter != iter->second.faces.end(); ++faceIter)
|
|
{
|
|
// TODO: actually I don't think this is the correct solution. We're allowed to sample light contributions from edges that aren't connected to this face.
|
|
// However we need to ensure we sample contributions from each face only once, and we need to check the angle between faces on a case-by-case basis.
|
|
// In fact I don't think we're interested in edges at all? Just in the faces that connect to a certain vertex.
|
|
if (*faceIter == face)
|
|
{
|
|
smoothEdges.push_back(edge);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gather lighting contributions from neigbouring faces
|
|
for (auto edgeIter = smoothEdges.begin(); edgeIter != smoothEdges.end(); ++edgeIter)
|
|
{
|
|
auto faces = edgeData.find(*edgeIter)->second.faces;
|
|
for (auto faceIter = faces.begin(); faceIter != faces.end(); ++faceIter) // FIXME: "this" face doesn't always appear in faces list, when it absolutely should!
|
|
{
|
|
const face_t* otherFace = *faceIter;
|
|
if (otherFace == face) // Skip the current face, we only sample it once
|
|
continue;
|
|
|
|
light += sample_lightmap(world, otherFace, faceBounds.find(otherFace)->second, point) + (0xFF - otherFace->baselight);
|
|
++numSamples;
|
|
}
|
|
}
|
|
|
|
return (unsigned char)(light / numSamples);
|
|
}
|
|
|
|
unsigned char compute_faceVertex_light2(const world_t* world, const face_t* face, unsigned short vertexIndex, const FaceBounds& faceBounds, const VertexFaces& vertexFaces)
|
|
{
|
|
const vertex_t* vertex = &world->vertices[vertexIndex];
|
|
auto vertexFaceIter = vertexFaces.find(vertex);
|
|
if (vertexFaceIter == vertexFaces.end())
|
|
return 0;
|
|
|
|
auto point = vertex->toVec();
|
|
|
|
// Sample this face's lighting contribution
|
|
unsigned int light = sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight);
|
|
int numSamples = 1;
|
|
|
|
const plane_t* thisPlane = &world->planes[face->plane_id];
|
|
vec3_t thisNormal = face->side ? -thisPlane->normal : thisPlane->normal;
|
|
|
|
// Gather light samples from other faces adjacent to this vertex
|
|
for (auto faceIter = vertexFaceIter->second.begin(); faceIter != vertexFaceIter->second.end(); ++faceIter)
|
|
{
|
|
const face_t* otherFace = *faceIter;
|
|
if (otherFace == face)
|
|
continue;
|
|
|
|
const plane_t* otherPlane = &world->planes[otherFace->plane_id];
|
|
vec3_t otherNormal = otherFace->side ? -otherPlane->normal : otherPlane->normal;
|
|
|
|
float dot = thisNormal.dotProduct(otherNormal);
|
|
if (dot < 0.5f)
|
|
continue; // Sharp edge, we don't want light contribution from this face
|
|
|
|
light += sample_lightmap(world, otherFace, faceBounds.find(otherFace)->second, point) + (0xFF - otherFace->baselight);
|
|
++numSamples;
|
|
}
|
|
|
|
return (unsigned char)(light / numSamples);
|
|
}
|
|
|
|
// Start with a hash set of all faces
|
|
// From the first face onward, group together faces into an interconnected surface based on angle between faces, using a flood-fill type of approach:
|
|
// - Add the first face to a surface face list
|
|
// - Take the next face from the surface face list (use index to go to next, don't remove it from the surface list)
|
|
// - For each face vertex, look up all other faces connected to that vertex
|
|
// - If the other face is not present in the original hash set, skip it (it was already added to a surface)
|
|
// - If the other face has an angle > 60 degrees with the current face, skip it (sharp edge)
|
|
// - Add the other face to the surface face list, remove it from the hash set
|
|
// - Repeat from step 'take the next face'
|
|
SurfaceList group_surfaces(const world_t* world, const VertexFaces& vertexFaces)
|
|
{
|
|
SurfaceList surfaces;
|
|
std::unordered_set<const face_t*> faceSet;
|
|
for (int i = 0; i < world->numFaces; ++i)
|
|
faceSet.insert(&world->faces[i]);
|
|
|
|
while (!faceSet.empty())
|
|
{
|
|
const face_t* firstFace = *faceSet.begin();
|
|
faceSet.erase(firstFace);
|
|
|
|
std::vector<const face_t*> surfaceFaces;
|
|
surfaceFaces.push_back(firstFace);
|
|
|
|
for (size_t faceIdx = 0; faceIdx < surfaceFaces.size(); ++faceIdx)
|
|
{
|
|
const face_t* thisFace = surfaceFaces[faceIdx];
|
|
const plane_t* thisPlane = &world->planes[thisFace->plane_id];
|
|
vec3_t thisNormal = thisFace->side ? -thisPlane->normal : thisPlane->normal;
|
|
|
|
for (int edgeListIdx = 0; edgeListIdx < thisFace->ledge_num; ++edgeListIdx)
|
|
{
|
|
int edgeIdx = world->edgeList[thisFace->ledge_id + edgeListIdx];
|
|
const edge_t* edge = &world->edges[abs(edgeIdx)];
|
|
|
|
for (int v = 0; v < 1; ++v)
|
|
{
|
|
unsigned short vertIndex = *(&edge->vertex0 + v);
|
|
|
|
const vertex_t* vertex = &world->vertices[vertIndex];
|
|
auto vertexFaceIter = vertexFaces.find(vertex);
|
|
if (vertexFaceIter == vertexFaces.end())
|
|
{
|
|
printf("Couldn't find list of faces for vertex %d, weird...\n", vertIndex);
|
|
continue;
|
|
}
|
|
|
|
for (auto faceIter = vertexFaceIter->second.begin(); faceIter != vertexFaceIter->second.end(); ++faceIter)
|
|
{
|
|
const face_t* otherFace = *faceIter;
|
|
if (faceSet.find(otherFace) == faceSet.end())
|
|
continue; // Face has already been added to a surface, skip it
|
|
|
|
const plane_t* otherPlane = &world->planes[otherFace->plane_id];
|
|
vec3_t otherNormal = otherFace->side ? -otherPlane->normal : otherPlane->normal;
|
|
float dot = thisNormal.dotProduct(otherNormal);
|
|
if (dot < 0.5f)
|
|
continue; // Sharp edge, face belongs to a different surface
|
|
|
|
// Add face to this surface and make sure it won't be reconsidered for any other surfaces
|
|
surfaceFaces.push_back(otherFace);
|
|
faceSet.erase(otherFace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Surface surface;
|
|
surface.faces.insert(surfaceFaces.begin(), surfaceFaces.end());
|
|
surfaces.push_back(surface);
|
|
}
|
|
|
|
return surfaces;
|
|
}
|
|
|
|
// To sample lightmap at any arbitrary world position:
|
|
// - Find the surface that the face belongs to
|
|
// - Select all faces from the surface where the position lies in its plane and is inside polygon boundaries
|
|
// - Sample those faces at the specified position (sample_lightmap) and average
|
|
std::vector<const face_t*> find_facesWithPoint(const world_t* world, const face_t* refFace, const SurfaceList& surfaces, Vec3 point)
|
|
{
|
|
std::vector<const face_t*> faces;
|
|
|
|
// Find the surface that the face belongs to
|
|
for (auto surfIter = surfaces.begin(); surfIter != surfaces.end(); ++surfIter)
|
|
{
|
|
if (surfIter->faces.find(refFace) == surfIter->faces.end())
|
|
continue;
|
|
|
|
// Select all faces from the surface where the point lies in its plane and is inside polygon boundaries
|
|
for (auto faceIter = surfIter->faces.begin(); faceIter != surfIter->faces.end(); ++faceIter)
|
|
{
|
|
const face_t* face = *faceIter;
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
|
|
// Check if the point lies on the face's plane
|
|
float pointPlaneDist = (point.x * plane->normal.x + point.y * plane->normal.y + point.z * plane->normal.z) - plane->dist;
|
|
if (fabs(pointPlaneDist) > FLT_EPSILON)
|
|
continue;
|
|
|
|
// Simple case first: check if this point lies on one of the face's edges
|
|
bool onEdge = false;
|
|
for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx)
|
|
{
|
|
int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx];
|
|
const edge_t* edge = &world->edges[abs(edgeIdx)];
|
|
|
|
Vec3 vert0 = world->vertices[edge->vertex0].toVec();
|
|
Vec3 vert1 = world->vertices[edge->vertex1].toVec();
|
|
|
|
Vec3 dir0 = vert1 - vert0;
|
|
Vec3 dir1 = point - vert0;
|
|
float mag0 = dir0.magnitude();
|
|
float mag1 = dir1.magnitude();
|
|
|
|
if (mag1 / mag0 <= 1.0f + FLT_EPSILON && dir0.dotProduct(dir1) >= (mag0 - FLT_EPSILON) * (mag1 - FLT_EPSILON))
|
|
onEdge = true;
|
|
}
|
|
|
|
if (onEdge)
|
|
faces.push_back(face);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return faces;
|
|
}
|
|
|
|
// Further improvements:
|
|
// - Reconstruct connected surfaces through vertices and edges, so we can fully sample all adjacent lightmaps.
|
|
// Right now we don't properly detect when one face has vertices halfway along the edge of an adjacent face.
|
|
// - Sample more lightmap points around each vertex to obtain a more representative average value
|
|
unsigned char compute_faceVertex_light3(const world_t* world, const face_t* refFace, const SurfaceList& surfaces, const FaceBounds& faceBounds, Vec3 point)
|
|
{
|
|
auto faces = find_facesWithPoint(world, refFace, surfaces, point);
|
|
if (faces.empty())
|
|
return 0;
|
|
|
|
unsigned int light = 0;
|
|
for (auto faceIter = faces.begin(); faceIter != faces.end(); ++faceIter)
|
|
{
|
|
const face_t* face = *faceIter;
|
|
light += sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight);
|
|
}
|
|
|
|
return (unsigned char)(light / faces.size());
|
|
}
|
|
|
|
// Find the list of all faces that contain the given point, i.e. the point lies on the face's plane and is contained within its polygon bounds
|
|
std::vector<const face_t*> world_facesWithPoint(const world_t* world, Vec3 point)
|
|
{
|
|
std::vector<const face_t*> faces;
|
|
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
|
|
{
|
|
const face_t* face = &world->faces[faceIdx];
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
|
|
// Check if the point lies on the face's plane (it's not strictly necessary to do this, but this check makes the whole function a lot faster)
|
|
double pointPlaneDist = ((double)point.x * plane->normal.x + (double)point.y * plane->normal.y + (double)point.z * plane->normal.z) - plane->dist;
|
|
if (fabs(pointPlaneDist) > 0.0001)
|
|
continue;
|
|
|
|
// Check if the point is contained within the face's polygon
|
|
double angleSum = 0;
|
|
for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx)
|
|
{
|
|
int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx];
|
|
|
|
Vec3 v0, v1;
|
|
if (edgeIdx > 0)
|
|
{
|
|
const edge_t* edge = &world->edges[edgeIdx];
|
|
v0 = world->vertices[edge->vertex0].toVec();
|
|
v1 = world->vertices[edge->vertex1].toVec();
|
|
}
|
|
else
|
|
{
|
|
const edge_t* edge = &world->edges[-edgeIdx];
|
|
v0 = world->vertices[edge->vertex1].toVec();
|
|
v1 = world->vertices[edge->vertex0].toVec();
|
|
}
|
|
|
|
Vec3 p0 = v0 - point;
|
|
Vec3 p1 = v1 - point;
|
|
double m0 = p0.magnitude();
|
|
double m1 = p1.magnitude();
|
|
|
|
if ((m0 * m1) <= 0.0001)
|
|
{
|
|
faces.push_back(face);
|
|
break;
|
|
}
|
|
|
|
double dot = p0.dotProduct(p1) / (m0 * m1);
|
|
angleSum += acos(dot);
|
|
}
|
|
|
|
if (fabs(2 * M_PI - angleSum) <= 0.0001)
|
|
faces.push_back(face);
|
|
}
|
|
|
|
return faces;
|
|
}
|
|
|
|
unsigned char compute_faceVertex_light4(const world_t* world, const face_t* refFace, const FaceBounds& faceBounds, Vec3 point)
|
|
{
|
|
auto faces = world_facesWithPoint(world, point);
|
|
if (faces.empty())
|
|
return 0;
|
|
|
|
const plane_t* refPlane = &world->planes[refFace->plane_id];
|
|
vec3_t refNormal = refFace->side ? -refPlane->normal : refPlane->normal;
|
|
|
|
unsigned int light = 0, numSamples = 0;
|
|
for (auto faceIter = faces.begin(); faceIter != faces.end(); ++faceIter)
|
|
{
|
|
const face_t* face = *faceIter;
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
vec3_t normal = face->side ? -plane->normal : plane->normal;
|
|
|
|
// Check if the face is at a shallow angle with the reference face
|
|
float dot = normal.dotProduct(refNormal);
|
|
if (dot < 0.5f)
|
|
continue;
|
|
|
|
light += sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight);
|
|
numSamples++;
|
|
}
|
|
|
|
// We should always end up with at least one sample (that from refFace itself), so if we divide by zero here something is very much wrong
|
|
return (unsigned char)(light / numSamples);
|
|
}
|