434 lines
14 KiB
C++
434 lines
14 KiB
C++
/* Copyright (C) 1996-1997 Id Software, Inc.
|
|
Copyright (C) 2017 Eric Wasylishen
|
|
|
|
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 2 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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
|
|
#include <cstdint>
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
|
|
#include <light/light2.hh>
|
|
#include <light/phong.hh>
|
|
#include <light/entities.hh>
|
|
#include <light/ltface.hh>
|
|
#include <light/ltface2.hh>
|
|
|
|
#include <common/polylib.hh>
|
|
#include <common/bsputils.hh>
|
|
|
|
#ifdef HAVE_EMBREE
|
|
#include <xmmintrin.h>
|
|
//#include <pmmintrin.h>
|
|
#endif
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <list>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <set>
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include <string>
|
|
|
|
#include <glm/vec2.hpp>
|
|
#include <glm/gtx/quaternion.hpp>
|
|
#include <glm/gtx/transform.hpp>
|
|
|
|
using namespace std;
|
|
|
|
glm::mat4x4 WorldToTexSpace(const bsp2_t *bsp, const bsp2_dface_t *f)
|
|
{
|
|
if (f->texinfo < 0 || f->texinfo >= bsp->numtexinfo) {
|
|
Q_assert_unreachable();
|
|
|
|
return glm::mat4x4();
|
|
}
|
|
|
|
const texinfo_t *tex = &bsp->texinfo[f->texinfo];
|
|
const plane_t plane = Face_Plane(bsp, f);
|
|
const vec_t *norm = plane.normal;
|
|
|
|
// [s]
|
|
// T * vec = [t]
|
|
// [distOffPlane]
|
|
// [?]
|
|
|
|
glm::mat4x4 T(tex->vecs[0][0], tex->vecs[1][0], norm[0], 0, // col 0
|
|
tex->vecs[0][1], tex->vecs[1][1], norm[1], 0, // col 1
|
|
tex->vecs[0][2], tex->vecs[1][2], norm[2], 0, // col 2
|
|
tex->vecs[0][3], tex->vecs[1][3], -plane.dist, 1 // col 3
|
|
);
|
|
return T;
|
|
}
|
|
|
|
// Rotates face1Norm about the line segment p0 <-> p1 so it is aligned with face0Norm
|
|
pair<bool, glm::mat4x4> RotationAboutLineSegment(glm::vec3 p0, glm::vec3 p1,
|
|
glm::vec3 face0Norm, glm::vec3 face1Norm)
|
|
{
|
|
// Get rotation angle. Quaternion rotates face1Norm to face0Norm
|
|
const glm::quat rotationQuat = glm::rotation(face1Norm, face0Norm);
|
|
|
|
// Any point on the line p0-p1 will work, so just use p0
|
|
const glm::mat4x4 toOrigin = glm::translate(-p0);
|
|
const glm::mat4x4 fromOrigin = glm::translate(p0);
|
|
|
|
const glm::mat4x4 composed = fromOrigin * glm::toMat4(rotationQuat) * toOrigin;
|
|
return make_pair(true, composed);
|
|
}
|
|
|
|
glm::mat4x4 TexSpaceToWorld(const bsp2_t *bsp, const bsp2_dface_t *f)
|
|
{
|
|
const glm::mat4x4 T = WorldToTexSpace(bsp, f);
|
|
|
|
if (glm::determinant(T) == 0) {
|
|
logprint("Bad texture axes on face:\n");
|
|
PrintFaceInfo(f, bsp);
|
|
Error("CreateFaceTransform");
|
|
}
|
|
|
|
return glm::inverse(T);
|
|
}
|
|
|
|
edgeToFaceMap_t MakeEdgeToFaceMap(const bsp2_t *bsp) {
|
|
edgeToFaceMap_t result;
|
|
|
|
for (int i = 0; i < bsp->numfaces; i++) {
|
|
const bsp2_dface_t *f = &bsp->dfaces[i];
|
|
|
|
// walk edges
|
|
for (int j = 0; j < f->numedges; j++) {
|
|
const int v0 = Face_VertexAtIndex(bsp, f, j);
|
|
const int v1 = Face_VertexAtIndex(bsp, f, (j + 1) % f->numedges);
|
|
|
|
const auto edge = make_pair(v0, v1);
|
|
auto &edgeFacesRef = result[edge];
|
|
|
|
Q_assert(find(begin(edgeFacesRef), end(edgeFacesRef), f) == end(edgeFacesRef));
|
|
edgeFacesRef.push_back(f);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
using bbox2 = pair<glm::vec2, glm::vec2>;
|
|
using bbox3 = pair<glm::vec3, glm::vec3>;
|
|
|
|
using contribface_stackframe_t = pair<const bsp2_dface_t *, glm::mat4x4>;
|
|
|
|
// Returns the next stack frames to process
|
|
vector<contribface_stackframe_t> SetupContributingFaces_R(const contribface_stackframe_t &frame,
|
|
const bsp2_t *bsp,
|
|
const edgeToFaceMap_t &edgeToFaceMap,
|
|
const bsp2_dface_t *refFace,
|
|
const aabb2 &refFaceTexBounds,
|
|
const aabb3 &refFaceWorldBounds,
|
|
vector<bool> *faceidx_handled,
|
|
vector<contributing_face_t> *result)
|
|
{
|
|
const bsp2_dface_t *f = frame.first;
|
|
const glm::mat4x4 &FWorldToRefWorld = frame.second;
|
|
|
|
const int currFnum = Face_GetNum(bsp, f);
|
|
if (faceidx_handled->at(currFnum))
|
|
return {};
|
|
|
|
// mark currentFace as handled
|
|
faceidx_handled->at(currFnum) = true;
|
|
|
|
if (!Face_IsLightmapped(bsp, f))
|
|
return {};
|
|
|
|
//printf("%s\n", Face_TextureName(bsp, f));
|
|
|
|
// transformFromRefFace will rotate `f` so it lies on the same plane as the reference face
|
|
|
|
// convert `f` texture space to world space, apply transformFromRefFace
|
|
// to rotate `f` onto the same plane as `f`,
|
|
// then convert that from refFace's world space to texture space
|
|
|
|
const glm::mat4x4 RefWorldToRefTex = WorldToTexSpace(bsp, refFace);
|
|
|
|
// now check each vertex's position in refFace's texture space.
|
|
// if no verts are within the range that could contribute to a sample in refFace
|
|
// we can stop recursion and skip adding `f` to the result vector.
|
|
const glm::mat4x4 FWorldToRefTex = RefWorldToRefTex * FWorldToRefWorld;
|
|
bool foundNearVert = false;
|
|
for (int j = 0; j < f->numedges; j++) {
|
|
const int v0 = Face_VertexAtIndex(bsp, f, j);
|
|
const glm::vec3 v0_position = Vertex_GetPos_E(bsp, v0);
|
|
|
|
const glm::vec4 v0InRefTex = FWorldToRefTex * glm::vec4(v0_position[0], v0_position[1], v0_position[2], 1.0);
|
|
const glm::vec2 v0InRefTex2f = glm::vec2(v0InRefTex);
|
|
|
|
// check distance to box
|
|
const bool near = refFaceTexBounds.grow(glm::vec2(16, 16)).contains(v0InRefTex2f);
|
|
if (near) {
|
|
|
|
const glm::vec3 v0InRefWorld = glm::vec3(FWorldToRefWorld * glm::vec4(v0_position[0], v0_position[1], v0_position[2], 1.0));
|
|
//const float worldDist = refFaceWorldBounds.exteriorDistance(v0InRefWorld);
|
|
|
|
//printf ("world distance: %f, tex dist: %f\n", worldDist, dist);
|
|
|
|
foundNearVert = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundNearVert) {
|
|
return {};
|
|
}
|
|
|
|
const glm::mat4x4 FTexToFWorld = TexSpaceToWorld(bsp, f);
|
|
const glm::mat4x4 FTexToRefTex = RefWorldToRefTex * FWorldToRefWorld * FTexToFWorld;
|
|
|
|
// add to result
|
|
contributing_face_t resAddition;
|
|
resAddition.contribFace = f;
|
|
resAddition.refFace = refFace;
|
|
resAddition.contribWorldToRefWorld = FWorldToRefWorld;
|
|
resAddition.contribTexToRefTex = FTexToRefTex;
|
|
resAddition.contribWorldToRefTex = FWorldToRefTex;
|
|
result->push_back(resAddition);
|
|
|
|
// walk edges
|
|
const glm::vec3 fNormal = Face_Normal_E(bsp, f);
|
|
|
|
vector<contribface_stackframe_t> nextframes;
|
|
|
|
for (int j = 0; j < f->numedges; j++) {
|
|
const int v0 = Face_VertexAtIndex(bsp, f, j);
|
|
const int v1 = Face_VertexAtIndex(bsp, f, (j + 1) % f->numedges);
|
|
|
|
const glm::vec3 v0pos = Vertex_GetPos_E(bsp, v0);
|
|
const glm::vec3 v1pos = Vertex_GetPos_E(bsp, v1);
|
|
|
|
auto it = edgeToFaceMap.find(make_pair(v1, v0));
|
|
if (it != edgeToFaceMap.end()) {
|
|
for (const bsp2_dface_t *neighbour : it->second) {
|
|
const int neighbourFNum = Face_GetNum(bsp, neighbour);
|
|
Q_assert(neighbour != f);
|
|
|
|
// Check if these faces are smoothed
|
|
if (!FacesSmoothed(f, neighbour)) {
|
|
// Never visit this face. Since we are exploring breadth-first from the reference face,
|
|
// once we have a non-smoothed edge, we don't want to "loop around" and include it later.
|
|
faceidx_handled->at(neighbourFNum) = true;
|
|
continue;
|
|
}
|
|
|
|
const glm::vec3 neighbourNormal = Face_Normal_E(bsp, neighbour);
|
|
|
|
const auto success = RotationAboutLineSegment(v0pos, v1pos, fNormal, neighbourNormal);
|
|
Q_assert(success.first);
|
|
const glm::mat4x4 NeighbourWorldToFWorld = success.second;
|
|
const glm::mat4x4 NeighbourWorldToRefWorld = FWorldToRefWorld * NeighbourWorldToFWorld;
|
|
|
|
nextframes.push_back(make_pair(neighbour, NeighbourWorldToRefWorld));
|
|
}
|
|
}
|
|
}
|
|
|
|
return nextframes;
|
|
}
|
|
|
|
aabb2 FaceTexBounds(const bsp2_t *bsp, const bsp2_dface_t *f)
|
|
{
|
|
aabb2 result;
|
|
const glm::mat4x4 T = WorldToTexSpace(bsp, f);
|
|
|
|
for (int j = 0; j < f->numedges; j++) {
|
|
const int v0 = Face_VertexAtIndex(bsp, f, j);
|
|
|
|
const glm::vec3 v0_position = Vertex_GetPos_E(bsp, v0);
|
|
|
|
const glm::vec4 v0Tex = T * glm::vec4(v0_position[0], v0_position[1], v0_position[2], 1.0);
|
|
const glm::vec2 v0Tex2f = glm::vec2(v0Tex);
|
|
|
|
result = result.expand(v0Tex2f);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
aabb3 FaceWorldBounds(const bsp2_t *bsp, const bsp2_dface_t *f)
|
|
{
|
|
aabb3 result;
|
|
|
|
for (int j = 0; j < f->numedges; j++) {
|
|
const int v0 = Face_VertexAtIndex(bsp, f, j);
|
|
const glm::vec3 v0_position = Vertex_GetPos_E(bsp, v0);
|
|
|
|
result = result.expand(v0_position);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* For the given face, find all other faces that can contribute samples.
|
|
*
|
|
* For each face that can contribute samples, makes a transformation matrix
|
|
* from that face's texture space to this face's.
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
vector<contributing_face_t> SetupContributingFaces(const bsp2_t *bsp, const bsp2_dface_t *face, const edgeToFaceMap_t &edgeToFaceMap)
|
|
{
|
|
vector<bool> faceidx_handled(static_cast<size_t>(bsp->numfaces), false);
|
|
vector<contributing_face_t> result;
|
|
|
|
const aabb2 refFaceTexBounds = FaceTexBounds(bsp, face);
|
|
const aabb3 refFaceWorldBounds = FaceWorldBounds(bsp, face);
|
|
|
|
// std::cout << "Face " << Face_GetNum(bsp, face)
|
|
// << " has tex bounds: "
|
|
// << refFaceTexBounds.min() << ", max:"
|
|
// << refFaceTexBounds.max() << std::endl
|
|
// << " has world bounds: "
|
|
// << refFaceWorldBounds.min() << ", max:"
|
|
// << refFaceWorldBounds.max() << std::endl;
|
|
|
|
// Breadth-first search, starting with `face`.
|
|
list<contribface_stackframe_t> queue { make_pair(face, glm::mat4x4()) };
|
|
|
|
while (!queue.empty()) {
|
|
const contribface_stackframe_t frame = queue.front();
|
|
queue.pop_front();
|
|
|
|
vector<contribface_stackframe_t> next = SetupContributingFaces_R(frame, bsp, edgeToFaceMap, face,
|
|
refFaceTexBounds, refFaceWorldBounds, &faceidx_handled, &result);
|
|
|
|
// Push the next frames on the back of the queue to get a BFS
|
|
for (const auto &frame : next) {
|
|
queue.push_back(frame);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
all_contrib_faces_t MakeContributingFaces(const bsp2_t *bsp)
|
|
{
|
|
const edgeToFaceMap_t edgeToFaceMap = MakeEdgeToFaceMap(bsp);
|
|
|
|
logprint("--- MakeContributingFaces ---\n");
|
|
|
|
all_contrib_faces_t result;
|
|
|
|
for (int i = 0; i < bsp->numfaces; i++) {
|
|
const bsp2_dface_t *f = &bsp->dfaces[i];
|
|
|
|
auto contrib = SetupContributingFaces(bsp, f, edgeToFaceMap);
|
|
|
|
result[f] = contrib;
|
|
// printf("face %d (%s) has %d contributing faces\n",
|
|
// (int)i,
|
|
// Face_TextureName(bsp, f),
|
|
// (int)contrib.size());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void AddFaceToBatch_R(const bsp2_t *bsp, const bsp2_dface_t *f, std::vector<bool> *faceidx_handled, std::vector<int> *batch)
|
|
{
|
|
const int fnum = Face_GetNum(bsp, f);
|
|
if (faceidx_handled->at(fnum))
|
|
return;
|
|
|
|
// add this face to the batch
|
|
faceidx_handled->at(fnum) = true;
|
|
batch->push_back(fnum);
|
|
|
|
// get touching faces either on the same plane, or phong shaded
|
|
for (const bsp2_dface_t *f2 : GetSmoothFaces(f)) {
|
|
AddFaceToBatch_R(bsp, f2, faceidx_handled, batch);
|
|
}
|
|
for (const bsp2_dface_t *f2 : GetPlaneFaces(f)) {
|
|
AddFaceToBatch_R(bsp, f2, faceidx_handled, batch);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Batch of faces that need to be lit together, on the same thread
|
|
*
|
|
* - If 2 faces are phong shaded across an edge, or line on the same plane and share an edge, they need to be lit together
|
|
*
|
|
* Light samples taken on one face might also contribute to other faces in a lightingbatch_t, but
|
|
* never to faces in another lightingbatch_t.
|
|
*/
|
|
|
|
batches_t MakeLightingBatches(const bsp2_t *bsp)
|
|
{
|
|
std::vector<bool> faceidx_handled(static_cast<size_t>(bsp->numfaces), false);
|
|
|
|
batches_t batches;
|
|
|
|
for (int i = 0; i < bsp->numfaces; i++) {
|
|
if (faceidx_handled.at(i))
|
|
continue;
|
|
|
|
std::vector<int> batch;
|
|
AddFaceToBatch_R(bsp, &bsp->dfaces[i], &faceidx_handled, &batch);
|
|
Q_assert(batch.size() > 0);
|
|
batches.push_back(batch);
|
|
}
|
|
|
|
//stats
|
|
|
|
int sum = 0;
|
|
int max = -1;
|
|
for (const auto &batch : batches) {
|
|
const int bs = static_cast<int>(batch.size());
|
|
|
|
sum += bs;
|
|
if (bs > max) {
|
|
max = bs;
|
|
}
|
|
}
|
|
Q_assert(sum == bsp->numfaces);
|
|
|
|
std::cout << "batches: " << batches.size() << " largest batch: " << max << std::endl;
|
|
|
|
return batches;
|
|
}
|
|
|
|
void *LightBatchThread(void *arg)
|
|
{
|
|
lightbatchthread_info_t *info = (lightbatchthread_info_t *)arg;
|
|
|
|
#ifdef HAVE_EMBREE
|
|
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
|
// _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
|
|
#endif
|
|
|
|
while (1) {
|
|
const int batchnum = GetThreadWork();
|
|
if (batchnum == -1)
|
|
break;
|
|
|
|
const auto &batch = info->all_batches.at(batchnum);
|
|
LightBatch(info->bsp, batch, info->all_contribFaces);
|
|
}
|
|
|
|
return NULL;
|
|
}
|