287 lines
7.8 KiB
C++
287 lines
7.8 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/light.hh>
|
|
#include <light/bounce.hh>
|
|
#include <light/ltface.hh>
|
|
|
|
#include <common/polylib.hh>
|
|
#include <common/bsputils.hh>
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <set>
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include <string>
|
|
|
|
#include <common/qvec.hh>
|
|
|
|
using namespace std;
|
|
using namespace polylib;
|
|
|
|
mutex radlights_lock;
|
|
static std::vector<bouncelight_t> radlights;
|
|
std::map<int, std::vector<int>> radlightsByFacenum;
|
|
|
|
class patch_t
|
|
{
|
|
public:
|
|
winding_t w;
|
|
qvec3d center;
|
|
qvec3d samplepoint; // 1 unit above center
|
|
qplane3d plane;
|
|
std::map<int, qvec3f> lightByStyle;
|
|
};
|
|
|
|
static unique_ptr<patch_t> MakePatch(const mbsp_t *bsp, const globalconfig_t &cfg, winding_t &w)
|
|
{
|
|
unique_ptr<patch_t> p{new patch_t};
|
|
p->w = std::move(w);
|
|
|
|
// cache some stuff
|
|
p->center = p->w.center();
|
|
p->plane = p->w.plane();
|
|
|
|
// nudge the cernter point 1 unit off
|
|
p->samplepoint = p->center + p->plane.normal;
|
|
|
|
// calculate direct light
|
|
|
|
p->lightByStyle = GetDirectLighting(bsp, cfg, p->samplepoint, p->plane.normal);
|
|
|
|
return p;
|
|
}
|
|
|
|
struct make_bounce_lights_args_t
|
|
{
|
|
const mbsp_t *bsp;
|
|
const globalconfig_t *cfg;
|
|
};
|
|
|
|
struct save_winding_args_t
|
|
{
|
|
vector<unique_ptr<patch_t>> *patches;
|
|
const mbsp_t *bsp;
|
|
const globalconfig_t *cfg;
|
|
};
|
|
|
|
static void SaveWindingFn(winding_t &w, void *userinfo)
|
|
{
|
|
save_winding_args_t *args = static_cast<save_winding_args_t *>(userinfo);
|
|
args->patches->push_back(MakePatch(args->bsp, *args->cfg, w));
|
|
}
|
|
|
|
static bool Face_ShouldBounce(const mbsp_t *bsp, const mface_t *face)
|
|
{
|
|
// make bounce light, only if this face is shadow casting
|
|
const modelinfo_t *mi = ModelInfoForFace(bsp, Face_GetNum(bsp, face));
|
|
if (!mi || !mi->shadow.boolValue()) {
|
|
return false;
|
|
}
|
|
|
|
if (!Face_IsLightmapped(bsp, face)) {
|
|
return false;
|
|
}
|
|
|
|
const char *texname = Face_TextureName(bsp, face);
|
|
if (!Q_strcasecmp("skip", texname)) {
|
|
return false;
|
|
}
|
|
|
|
// check for "_bounce" "-1"
|
|
if (extended_texinfo_flags[face->texinfo].no_bounce) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
qvec3b Face_LookupTextureColor(const mbsp_t *bsp, const mface_t *face)
|
|
{
|
|
auto it = img::find(Face_TextureName(bsp, face));
|
|
|
|
if (it) {
|
|
return it->meta.averageColor;
|
|
}
|
|
|
|
return { 127 };
|
|
}
|
|
|
|
template<typename T>
|
|
static void AddBounceLight(const T &pos, const std::map<int, qvec3f> &colorByStyle, const qvec3d &surfnormal,
|
|
vec_t area, const mface_t *face, const mbsp_t *bsp)
|
|
{
|
|
for (const auto &styleColor : colorByStyle) {
|
|
Q_assert(styleColor.second[0] >= 0);
|
|
Q_assert(styleColor.second[1] >= 0);
|
|
Q_assert(styleColor.second[2] >= 0);
|
|
}
|
|
Q_assert(area > 0);
|
|
|
|
bouncelight_t l;
|
|
l.poly = GLM_FacePoints(bsp, face);
|
|
l.poly_edgeplanes = GLM_MakeInwardFacingEdgePlanes(l.poly);
|
|
l.pos = pos;
|
|
l.colorByStyle = colorByStyle;
|
|
|
|
qvec3f componentwiseMaxColor{};
|
|
for (const auto &styleColor : colorByStyle) {
|
|
for (int i = 0; i < 3; i++) {
|
|
if (styleColor.second[i] > componentwiseMaxColor[i]) {
|
|
componentwiseMaxColor[i] = styleColor.second[i];
|
|
}
|
|
}
|
|
}
|
|
l.componentwiseMaxColor = componentwiseMaxColor;
|
|
l.surfnormal = surfnormal;
|
|
l.area = area;
|
|
l.bounds = qvec3d(0);
|
|
|
|
if (!novisapprox) {
|
|
l.bounds = EstimateVisibleBoundsAtPoint(pos);
|
|
}
|
|
|
|
unique_lock<mutex> lck{radlights_lock};
|
|
radlights.push_back(l);
|
|
|
|
const int lastBounceLightIndex = static_cast<int>(radlights.size()) - 1;
|
|
radlightsByFacenum[Face_GetNum(bsp, face)].push_back(lastBounceLightIndex);
|
|
}
|
|
|
|
static void *MakeBounceLightsThread(void *arg)
|
|
{
|
|
const mbsp_t *bsp = static_cast<make_bounce_lights_args_t *>(arg)->bsp;
|
|
const globalconfig_t &cfg = *static_cast<make_bounce_lights_args_t *>(arg)->cfg;
|
|
|
|
while (1) {
|
|
int i = GetThreadWork();
|
|
if (i == -1)
|
|
break;
|
|
|
|
const mface_t *face = BSP_GetFace(bsp, i);
|
|
|
|
if (!Face_ShouldBounce(bsp, face)) {
|
|
continue;
|
|
}
|
|
|
|
vector<unique_ptr<patch_t>> patches;
|
|
|
|
winding_t winding = winding_t::from_face(bsp, face);
|
|
|
|
// grab some info about the face winding
|
|
const vec_t facearea = winding.area();
|
|
|
|
// degenerate face
|
|
if (!facearea) {
|
|
continue;
|
|
}
|
|
|
|
qplane3d faceplane = winding.plane();
|
|
|
|
qvec3d facemidpoint = winding.center();
|
|
facemidpoint += faceplane.normal; // lift 1 unit
|
|
|
|
save_winding_args_t args;
|
|
args.patches = &patches;
|
|
args.bsp = bsp;
|
|
args.cfg = &cfg;
|
|
|
|
winding.dice(64.0f, SaveWindingFn, &args);
|
|
|
|
// average them, area weighted
|
|
map<int, qvec3f> sum;
|
|
float totalarea = 0;
|
|
|
|
for (const auto &patch : patches) {
|
|
const float patcharea = patch->w.area();
|
|
totalarea += patcharea;
|
|
|
|
for (const auto &styleColor : patch->lightByStyle) {
|
|
sum[styleColor.first] = sum[styleColor.first] + (styleColor.second * patcharea);
|
|
}
|
|
// fmt::print(" {} {} {}\n", patch->directlight[0], patch->directlight[1], patch->directlight[2]);
|
|
}
|
|
|
|
// avoid small, or zero-area patches ("sum" would be nan)
|
|
if (totalarea < 1) {
|
|
continue;
|
|
}
|
|
|
|
for (auto &styleColor : sum) {
|
|
styleColor.second *= (1.0f / totalarea);
|
|
styleColor.second /= 255.0f;
|
|
}
|
|
|
|
// lerp between gray and the texture color according to `bouncecolorscale` (0 = use gray, 1 = use texture color)
|
|
qvec3f texturecolor = qvec3f(Face_LookupTextureColor(bsp, face)) / 255.0f;
|
|
qvec3f blendedcolor = mix(qvec3f{127.f / 255.f}, texturecolor, cfg.bouncecolorscale.floatValue());
|
|
|
|
// final colors to emit
|
|
map<int, qvec3f> emitcolors;
|
|
for (const auto &styleColor : sum) {
|
|
qvec3f emitcolor{};
|
|
for (int k = 0; k < 3; k++) {
|
|
emitcolor[k] = styleColor.second[k] * blendedcolor[k];
|
|
}
|
|
emitcolors[styleColor.first] = emitcolor;
|
|
}
|
|
|
|
AddBounceLight(facemidpoint, emitcolors, faceplane.normal, facearea, face, bsp);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const std::vector<bouncelight_t> &BounceLights()
|
|
{
|
|
return radlights;
|
|
}
|
|
|
|
const std::vector<int> &BounceLightsForFaceNum(int facenum)
|
|
{
|
|
const auto &vec = radlightsByFacenum.find(facenum);
|
|
if (vec != radlightsByFacenum.end()) {
|
|
return vec->second;
|
|
}
|
|
|
|
static std::vector<int> empty;
|
|
return empty;
|
|
}
|
|
|
|
void MakeBounceLights(const globalconfig_t &cfg, const mbsp_t *bsp)
|
|
{
|
|
LogPrint("--- MakeBounceLights ---\n");
|
|
|
|
make_bounce_lights_args_t args{
|
|
bsp, &cfg}; // mxd. https://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.html
|
|
|
|
RunThreadsOn(0, bsp->dfaces.size(), MakeBounceLightsThread, (void *)&args);
|
|
|
|
LogPrint("{} bounce lights created\n", radlights.size());
|
|
}
|