Merge branch 'brushbsp' of https://github.com/ericwa/ericw-tools into brushbsp

This commit is contained in:
Jonathan 2022-11-17 13:55:37 -05:00
commit 328fa6ade6
43 changed files with 809 additions and 109 deletions

2
3rdparty/fmt vendored

@ -1 +1 @@
Subproject commit 7bdf0628b1276379886c7f6dda2cef2b3b374f0b
Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50

View File

@ -37,7 +37,7 @@ if (UNIX)
endif (UNIX)
# set our C/C++ dialects
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)

View File

@ -1,5 +1,5 @@
image:
- Visual Studio 2019
- Visual Studio 2022
platform:
- x64

View File

@ -18,7 +18,7 @@ mkdir cmakebuild
cd cmakebuild
cmake .. -T v142 -Dembree_DIR="C:\embree-3.12.1.x64.vc14.windows" -DTBB_DIR="C:\tbb\cmake" -DCMAKE_GENERATOR_PLATFORM=x64 -DENABLE_LIGHTPREVIEW=NO -DQt5Widgets_DIR="C:\Qt\5.8\msvc2013_64\lib\cmake\Qt5Widgets"
cmake .. -T v143 -Dembree_DIR="C:\embree-3.12.1.x64.vc14.windows" -DTBB_DIR="C:\tbb\cmake" -DCMAKE_GENERATOR_PLATFORM=x64 -DENABLE_LIGHTPREVIEW=NO -DQt5Widgets_DIR="C:\Qt\5.8\msvc2013_64\lib\cmake\Qt5Widgets"
$cmakePlatform = "x64"

View File

@ -1693,14 +1693,14 @@ const bspversion_t bspver_qbism{Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II
bool surfflags_t::needs_write() const
{
return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || phong_angle ||
return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || !surflight_rescale || phong_angle ||
phong_angle_concave || minlight || !qv::emptyExact(minlight_color) || light_alpha || maxlight || lightcolorscale != 1.0;
}
static auto as_tuple(const surfflags_t &flags)
{
return std::tie(flags.native, flags.is_nodraw, flags.is_hintskip, flags.is_hint, flags.no_dirt, flags.no_shadow, flags.no_bounce, flags.no_minlight, flags.no_expand,
flags.light_ignore, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale);
flags.light_ignore, flags.surflight_rescale, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale);
}
bool surfflags_t::operator<(const surfflags_t &other) const

View File

@ -228,7 +228,7 @@ static faceextents_t get_face_extents(const mbsp_t &bsp, const bspxentries_t &bs
return {face, bsp, bspx.lmwidth, bspx.lmheight, bspx.world_to_lm_space};
}
if (!use_bspx) {
return {face, bsp, 16.0};
return {face, bsp, LMSCALE_DEFAULT};
}
return {face, bsp,

View File

@ -622,6 +622,20 @@ void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed)
} while (out - decompressed < row);
}
bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num)
{
auto &lump_bytes = entries.at("DECOUPLED_LM");
auto stream = imemstream(lump_bytes.data(), lump_bytes.size());
stream.seekg(face_num * sizeof(bspx_decoupled_lm_perface));
stream >> endianness<std::endian::little>;
bspx_decoupled_lm_perface result;
stream >= result;
return result;
}
qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex)
{
/*
@ -901,3 +915,27 @@ qvec3f faceextents_t::LMCoordToWorld(qvec2f lm) const
const qvec4f res = lmToWorldMatrix * lmPadded;
return res;
}
/**
* Samples the lightmap at an integer coordinate
*/
qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord)
{
int pixel = coord[0] + (coord[1] * faceextents.width());
const uint8_t* data = bsp->dlightdata.data();
if (bsp->loadversion->game->has_rgb_lightmap) {
return qvec3f{
data[byte_offset_of_face + (pixel * 3) + 0],
data[byte_offset_of_face + (pixel * 3) + 1],
data[byte_offset_of_face + (pixel * 3) + 2]
};
} else {
return qvec3f{
data[byte_offset_of_face + pixel],
data[byte_offset_of_face + pixel],
data[byte_offset_of_face + pixel]
};
}
}

View File

@ -304,16 +304,14 @@ void stat_tracker_t::print_stats()
stats_printed = true;
auto old = std::locale::global(std::locale("en_US.UTF-8"));
// add 8 char padding just to keep it away from the left side
size_t number_padding = number_of_digit_padding() + 4;
for (auto &stat : stats) {
if (stat.show_even_if_zero || stat.count) {
print(flag::STAT, "{}{:{}L} {}\n", stat.is_warning ? "WARNING: " : "", stat.count, stat.is_warning ? 0 : number_padding, stat.name);
print(flag::STAT, "{}{:{}} {}\n", stat.is_warning ? "WARNING: " : "", fmt::group_digits(stat.count.load()), stat.is_warning ? 0 : number_padding, stat.name);
}
}
std::locale::global(old);
}
stat_tracker_t::~stat_tracker_t()

View File

@ -224,11 +224,11 @@ struct fmt::formatter<aabb<T, Dim>> : formatter<qvec<T, Dim>>
template<typename FormatContext>
auto format(const aabb<T, Dim> &b, FormatContext &ctx) -> decltype(ctx.out())
{
format_to(ctx.out(), "{{mins: ");
formatter<qvec<T, Dim>>::format(b.mins(), ctx);
format_to(ctx.out(), ", maxs: ");
formatter<qvec<T, Dim>>::format(b.maxs(), ctx);
format_to(ctx.out(), "}}");
fmt::format_to(ctx.out(), "{{mins: ");
fmt::formatter<qvec<T, Dim>>::format(b.mins(), ctx);
fmt::format_to(ctx.out(), ", maxs: ");
fmt::formatter<qvec<T, Dim>>::format(b.maxs(), ctx);
fmt::format_to(ctx.out(), "}}");
return ctx.out();
}
};

View File

@ -185,6 +185,10 @@ struct surfflags_t
// this face doesn't receive light
bool light_ignore;
// if true, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface normal
// if false, use a more natural angle falloff of 0% at 90 degrees
bool surflight_rescale = true;
// if non zero, enables phong shading and gives the angle threshold to use
vec_t phong_angle;
@ -351,18 +355,18 @@ struct fmt::formatter<bspversion_t>
auto format(const bspversion_t &v, FormatContext &ctx) -> decltype(ctx.out())
{
if (v.name) {
format_to(ctx.out(), "{} ", v.name);
fmt::format_to(ctx.out(), "{} ", v.name);
}
// Q2-esque BSPs are printed as, ex, IBSP:38
if (v.version.has_value()) {
char ident[5] = {(char)(v.ident & 0xFF), (char)((v.ident >> 8) & 0xFF), (char)((v.ident >> 16) & 0xFF),
(char)((v.ident >> 24) & 0xFF), '\0'};
return format_to(ctx.out(), "{}:{}", ident, v.version.value());
return fmt::format_to(ctx.out(), "{}:{}", ident, v.version.value());
}
// Q1-esque BSPs are printed as, ex, bsp29
return format_to(ctx.out(), "{}", v.short_name);
return fmt::format_to(ctx.out(), "{}", v.short_name);
}
};
@ -406,7 +410,7 @@ struct texvec : qmat<T, 2, 4>
// Fmt support
template<class T>
struct fmt::formatter<texvec<T>> : formatter<qmat<T, 2, 4>>
struct fmt::formatter<texvec<T>> : fmt::formatter<qmat<T, 2, 4>>
{
};

View File

@ -20,6 +20,7 @@
#pragma once
#include <common/bspfile.hh>
#include <common/bspxfile.hh>
#include <common/mathlib.hh>
#include <common/qvec.hh>
#include <common/aabb.hh>
@ -107,6 +108,8 @@ void Face_DebugPrint(const mbsp_t *bsp, const mface_t *face);
void CompressRow(const uint8_t *vis, const size_t numbytes, std::back_insert_iterator<std::vector<uint8_t>> it);
void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed);
bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num);
/* ======================================================================== */
qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex);
@ -122,6 +125,8 @@ constexpr size_t MAXDIMENSION = 255 + 1;
struct world_units_per_luxel_t {};
constexpr float LMSCALE_DEFAULT = 16.0f;
class faceextents_t
{
public:
@ -150,3 +155,5 @@ public:
qvec2f worldToLMCoord(qvec3f world) const;
qvec3f LMCoordToWorld(qvec2f lm) const;
};
qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord);

View File

@ -108,25 +108,8 @@ time_point I_FloatTime();
* BYTE ORDER FUNCTIONS
* ============================================================================
*/
// C++20 polyfill
#if defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L
#include <bit>
#else
namespace std
{
enum class endian
{
little = 0,
big = 1,
#ifdef __BIG_ENDIAN__
native = big
#else
native = little
#endif
};
} // namespace std
#endif
#include <bit>
// Binary streams; by default, streams use the native endianness
// (unchanged bytes) but can be changed to a specific endianness

View File

@ -113,6 +113,6 @@ struct fmt::formatter<fs::path>
template<typename FormatContext>
auto format(const fs::path &p, FormatContext &ctx)
{
return format_to(ctx.out(), "{}", p.string());
return fmt::format_to(ctx.out(), "{}", p.string());
}
};

View File

@ -74,15 +74,15 @@ template<typename... Args>
inline void print(flag type, const char *fmt, const Args &...args)
{
if (mask & type) {
print(type, fmt::format(fmt, std::forward<const Args &>(args)...).c_str());
print(type, fmt::format(fmt::runtime(fmt), std::forward<const Args &>(args)...).c_str());
}
}
// format print to default targets
template<typename... Args>
inline void print(const char *fmt, const Args &...args)
inline void print(const char *formt, const Args &...args)
{
print(flag::DEFAULT, fmt::format(fmt, std::forward<const Args &>(args)...).c_str());
print(flag::DEFAULT, fmt::format(fmt::runtime(formt), std::forward<const Args &>(args)...).c_str());
}
void header(const char *name);
@ -182,7 +182,7 @@ struct stat_tracker_t
template<typename... Args>
[[noreturn]] inline void Error(const char *fmt, const Args &...args)
{
auto formatted = fmt::format(fmt, std::forward<const Args &>(args)...);
auto formatted = fmt::format(fmt::runtime(fmt), std::forward<const Args &>(args)...);
Error(formatted.c_str());
}

View File

@ -93,13 +93,13 @@ struct fmt::formatter<parser_source_location>
auto format(const parser_source_location &v, FormatContext &ctx) -> decltype(ctx.out())
{
if (v.source_name) {
format_to(ctx.out(), "{}", *v.source_name.get());
fmt::format_to(ctx.out(), "{}", *v.source_name.get());
} else {
format_to(ctx.out(), "unknown/unset location");
fmt::format_to(ctx.out(), "unknown/unset location");
}
if (v.line_number.has_value()) {
format_to(ctx.out(), "[line {}]", v.line_number.value());
fmt::format_to(ctx.out(), "[line {}]", v.line_number.value());
}
return ctx.out();

View File

@ -341,11 +341,11 @@ struct fmt::formatter<qvec<T, N>>
auto format(const qvec<T, N> &p, FormatContext &ctx) -> decltype(ctx.out())
{
for (size_t i = 0; i < N - 1; i++) {
format_to(ctx.out(), "{}", p[i]);
format_to(ctx.out(), " ");
fmt::format_to(ctx.out(), "{}", p[i]);
fmt::format_to(ctx.out(), " ");
}
return format_to(ctx.out(), "{}", p[N - 1]);
return fmt::format_to(ctx.out(), "{}", p[N - 1]);
}
};
@ -838,9 +838,9 @@ struct fmt::formatter<qplane3<T>> : formatter<qvec<T, 3>>
template<typename FormatContext>
auto format(const qplane3<T> &p, FormatContext &ctx) -> decltype(ctx.out())
{
format_to(ctx.out(), "{{normal: ");
formatter<qvec<T, 3>>::format(p.normal, ctx);
format_to(ctx.out(), ", dist: {}}}", p.dist);
fmt::format_to(ctx.out(), "{{normal: ");
fmt::formatter<qvec<T, 3>>::format(p.normal, ctx);
fmt::format_to(ctx.out(), ", dist: {}}}", p.dist);
return ctx.out();
}
};
@ -1026,12 +1026,12 @@ struct fmt::formatter<qmat<T, NRow, NCol>> : formatter<qvec<T, NCol>>
auto format(const qmat<T, NRow, NCol> &p, FormatContext &ctx) -> decltype(ctx.out())
{
for (size_t i = 0; i < NRow; i++) {
format_to(ctx.out(), "[ ");
formatter<qvec<T, NCol>>::format(p.row(i), ctx);
format_to(ctx.out(), " ]");
fmt::format_to(ctx.out(), "[ ");
fmt::formatter<qvec<T, NCol>>::format(p.row(i), ctx);
fmt::format_to(ctx.out(), " ]");
if (i != NRow - 1) {
format_to(ctx.out(), "\n");
fmt::format_to(ctx.out(), "\n");
}
}

View File

@ -34,6 +34,7 @@
#include <limits>
#include <optional>
#include <unordered_set>
#include <functional>
namespace settings
{

View File

@ -32,5 +32,6 @@
// public functions
void ResetBounce();
const std::vector<surfacelight_t> &BounceLights();
void MakeBounceLights(const settings::worldspawn_keys &cfg, const mbsp_t *bsp);

View File

@ -122,6 +122,7 @@ public:
* Stores the RGB values to determine the light color
*/
void ResetLightEntities();
std::string TargetnameForLightStyle(int style);
std::vector<std::unique_ptr<light_t>> &GetLights();
std::vector<sun_t> &GetSuns();

View File

@ -161,7 +161,8 @@ enum class debugmodes
debugoccluded,
debugneighbours,
phong_tangents,
phong_bitangents
phong_bitangents,
mottle
};
enum class lightfile
@ -385,6 +386,7 @@ public:
setting_func phongdebug_obj;
setting_func debugoccluded;
setting_func debugneighbours;
setting_func debugmottle;
light_settings();
@ -425,5 +427,6 @@ const modelinfo_t *ModelInfoForFace(const mbsp_t *bsp, int facenum);
const img::texture *Face_Texture(const mbsp_t *bsp, const mface_t *face);
const qvec3b &Face_LookupTextureColor(const mbsp_t *bsp, const mface_t *face);
const qvec3d &Face_LookupTextureBounceColor(const mbsp_t *bsp, const mface_t *face);
void light_reset();
int light_main(int argc, const char **argv);
int light_main(const std::vector<std::string> &args);

View File

@ -61,3 +61,4 @@ void FinishLightmapSurface(const mbsp_t *bsp, lightsurf_t *lightsurf);
void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup,
bspx_decoupled_lm_perface *facesup_decoupled, lightsurf_t *lightsurf, const faceextents_t &extents,
const faceextents_t &output_extents);
void ResetLtFace();

View File

@ -79,3 +79,4 @@ public:
};
const face_cache_t &FaceCacheForFNum(int fnum);
void ResetPhong();

View File

@ -49,6 +49,7 @@ struct surfacelight_t
bool rescale;
};
void ResetSurflight();
std::vector<surfacelight_t> &GetSurfaceLights();
std::optional<std::tuple<int32_t, int32_t>> IsSurfaceLitFace(const mbsp_t *bsp, const mface_t *face);
const std::vector<int> &SurfaceLightsForFaceNum(int facenum);

View File

@ -27,6 +27,7 @@
#include <common/polylib.hh>
#include <vector>
void ResetEmbree();
void Embree_TraceInit(const mbsp_t *bsp);
hitresult_t Embree_TestSky(const qvec3d &start, const qvec3d &dirn, const modelinfo_t *self, const mface_t **face_out);
hitresult_t Embree_TestLight(const qvec3d &start, const qvec3d &stop, const modelinfo_t *self);

View File

@ -368,9 +368,9 @@ struct fmt::formatter<qbsp_plane_t> : formatter<qplane3d>
template<typename FormatContext>
auto format(const qbsp_plane_t &p, FormatContext &ctx) -> decltype(ctx.out())
{
format_to(ctx.out(), "<");
formatter<qplane3d>::format(p.get_plane(), ctx);
format_to(ctx.out(), ", type: {}>", p.get_type());
fmt::format_to(ctx.out(), "<");
fmt::formatter<qplane3d>::format(p.get_plane(), ctx);
fmt::format_to(ctx.out(), ", type: {}>", p.get_type());
return ctx.out();
}
};

View File

@ -44,6 +44,12 @@ mutex bouncelights_lock;
static std::vector<surfacelight_t> bouncelights;
static std::atomic_size_t bouncelightpoints;
void ResetBounce()
{
bouncelights.clear();
bouncelightpoints = 0;
}
static bool Face_ShouldBounce(const mbsp_t *bsp, const mface_t *face)
{
// make bounce light, only if this face is shadow casting

View File

@ -33,6 +33,27 @@ std::vector<std::unique_ptr<light_t>> all_lights;
std::vector<sun_t> all_suns;
std::vector<entdict_t> entdicts;
std::vector<entdict_t> radlights;
static std::vector<std::pair<std::string, int>> lightstyleForTargetname;
static std::vector<std::unique_ptr<light_t>> surfacelight_templates;
static std::ofstream surflights_dump_file;
static fs::path surflights_dump_filename;
/**
* Resets global data in this file
*/
void ResetLightEntities()
{
all_lights.clear();
all_suns.clear();
entdicts.clear();
radlights.clear();
lightstyleForTargetname.clear();
surfacelight_templates.clear();
surflights_dump_file = {};
surflights_dump_filename.clear();
}
std::vector<std::unique_ptr<light_t>> &GetLights()
{
@ -104,8 +125,6 @@ void light_t::expandAABB(const qvec3d &pt) { bounds += pt; }
* ============================================================================
*/
static std::vector<std::pair<std::string, int>> lightstyleForTargetname;
entdict_t &WorldEnt()
{
if (entdicts.size() == 0 || entdicts.at(0).get("classname") != "worldspawn") {
@ -1152,16 +1171,11 @@ void WriteEntitiesToString(const settings::worldspawn_keys &cfg, mbsp_t *bsp)
* =======================================================================
*/
static std::vector<std::unique_ptr<light_t>> surfacelight_templates;
const std::vector<std::unique_ptr<light_t>> &GetSurfaceLightTemplates()
{
return surfacelight_templates;
}
static std::ofstream surflights_dump_file;
static fs::path surflights_dump_filename;
static void SurfLights_WriteEntityToFile(light_t *entity, const qvec3d &pos)
{
Q_assert(entity->epairs != nullptr);

View File

@ -351,7 +351,14 @@ light_settings::light_settings()
CheckNoDebugModeSet();
debugmode = debugmodes::debugneighbours;
},
&debug_group, "save neighboring faces data to lightmap (requires -debugface)"}
&debug_group, "save neighboring faces data to lightmap (requires -debugface)"},
debugmottle{this, "debugmottle",
[&](source) {
CheckNoDebugModeSet();
debugmode = debugmodes::mottle;
},
&debug_group, "save mottle pattern to lightmap"}
{
}
@ -707,10 +714,10 @@ static void FindModelInfo(const mbsp_t *bsp)
float lightmapscale = WorldEnt().get_int("_lightmap_scale");
if (!lightmapscale)
lightmapscale = 16; /* the default */
lightmapscale = LMSCALE_DEFAULT; /* the default */
if (lightmapscale <= 0)
FError("lightmap scale is 0 or negative\n");
if (light_options.lightmap_scale.isChanged() || lightmapscale != 16)
if (light_options.lightmap_scale.isChanged() || lightmapscale != LMSCALE_DEFAULT)
logging::print("Forcing lightmap scale of {}qu\n", lightmapscale);
/*I'm going to do this check in the hopes that there's a benefit to cheaper scaling in engines (especially software
* ones that might be able to just do some mip hacks). This tool doesn't really care.*/
@ -1065,6 +1072,9 @@ static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_
if (val.contains("light_ignore")) {
flags.light_ignore = val.at("light_ignore").get<bool>();
}
if (val.contains("surflight_rescale")) {
flags.surflight_rescale = val.at("surflight_rescale").get<bool>();
}
if (val.contains("phong_angle")) {
flags.phong_angle = val.at("phong_angle").get<vec_t>();
}
@ -1458,6 +1468,51 @@ void load_textures(const mbsp_t *bsp)
}
}
/**
* Resets globals in this file
*/
static void ResetLight()
{
dirt_in_use = false;
light_surfaces.clear();
faces_sup.clear();
facesup_decoupled_global.clear();
filebase.clear();
file_p = 0;
file_end = 0;
lit_filebase.clear();
lit_file_p = 0;
lit_file_end = 0;
lux_filebase.clear();
lux_file_p = 0;
lux_file_end = 0;
modelinfo.clear();
tracelist.clear();
selfshadowlist.clear();
shadowworldonlylist.clear();
switchableshadowlist.clear();
extended_texinfo_flags.clear();
dump_facenum = -1;
dump_vertnum = -1;
}
void light_reset()
{
ResetBounce();
ResetLightEntities();
ResetLight();
ResetLtFace();
ResetPhong();
ResetSurflight();
ResetEmbree();
}
/*
* ==================
* main
@ -1466,6 +1521,8 @@ void load_textures(const mbsp_t *bsp)
*/
int light_main(int argc, const char **argv)
{
light_reset();
bspdata_t bspdata;
light_options.preinitialize(argc, argv);

View File

@ -703,7 +703,7 @@ static std::unique_ptr<lightsurf_t> Lightsurf_Init(const modelinfo_t *modelinfo,
} else {
lightsurf->extents = faceextents_t(*face, *bsp, lightsurf->lightmapscale);
}
lightsurf->vanilla_extents = faceextents_t(*face, *bsp, 16.0);
lightsurf->vanilla_extents = faceextents_t(*face, *bsp, LMSCALE_DEFAULT);
CalcPoints(modelinfo, modelinfo->offset, lightsurf.get(), bsp, face);
@ -1395,6 +1395,110 @@ static void LightFace_Sky(const sun_t *sun, lightsurf_t *lightsurf, lightmapdict
}
}
// Mottle
static int mod_round_to_neg_inf(int x, int y) {
assert(y > 0);
if (x >= 0) {
return x % y;
}
// e.g. with mod_round_to_neg_inf(-7, 3) we want +2
const int temp = (-x) % y;
return y - temp;
}
constexpr int mottle_texsize = 256;
/**
* integers 0 through 255 shuffled with Python:
*
* import random
* a = list(range(0, 256))
* random.shuffle(a)
*/
static constexpr uint8_t MottlePermutation256[] = {11, 255, 250, 82, 217, 9, 144, 93, 136, 153, 55, 71, 73, 204, 96,
180, 126, 8, 50, 46, 113, 91, 238, 143, 30, 215, 191, 243, 65, 58, 208, 33, 86, 1, 182, 118, 83, 115, 207, 52, 94,
112, 205, 48, 99, 254, 117, 101, 157, 140, 72, 242, 244, 154, 10, 135, 155, 168, 125, 183, 148, 116, 187, 166, 25,
156, 177, 231, 165, 57, 221, 105, 28, 211, 127, 41, 142, 253, 146, 87, 122, 229, 162, 137, 194, 174, 167, 15, 220,
26, 235, 3, 39, 80, 88, 42, 202, 12, 97, 53, 70, 123, 170, 110, 214, 192, 173, 84, 169, 188, 64, 102, 147, 158, 100,
69, 213, 193, 43, 20, 13, 237, 171, 103, 32, 190, 223, 150, 131, 206, 85, 124, 163, 18, 139, 132, 79, 29, 216, 232,
178, 74, 24, 141, 201, 181, 152, 4, 7, 159, 134, 212, 226, 245, 164, 239, 47, 66, 27, 40, 197, 81, 78, 219, 228,
241, 121, 23, 120, 230, 76, 252, 199, 184, 45, 203, 161, 89, 16, 21, 119, 5, 209, 196, 68, 130, 195, 176, 225, 233,
128, 22, 248, 179, 249, 61, 108, 138, 145, 31, 49, 107, 56, 172, 224, 210, 6, 160, 189, 104, 200, 44, 175, 133, 77,
62, 106, 92, 186, 227, 14, 38, 247, 37, 17, 222, 36, 75, 129, 185, 251, 240, 54, 151, 2, 98, 149, 0, 63, 218, 60,
198, 19, 59, 90, 246, 234, 67, 51, 109, 95, 236, 35, 34, 114, 111};
/**
* Return a noise texture value from 0-47.
*
* Vanilla Q2 tools just called (rand() % 48) per-luxel, which generates seams
* and scales in size with lightmap scale.
*
* Replacement code uses "value noise", generating a tiling 3D texture
* in world space.
*/
static float Mottle(const qvec3d &position)
{
#if 0
return rand() % 48;
#else
const float world_to_tex = 1/16.0f;
qvec3d texspace_pos = position * world_to_tex;
int coord_floor_x = static_cast<int>(floor(texspace_pos[0]));
int coord_floor_y = static_cast<int>(floor(texspace_pos[1]));
int coord_floor_z = static_cast<int>(floor(texspace_pos[2]));
float coord_frac_x = static_cast<float>(texspace_pos[0] - coord_floor_x);
float coord_frac_y = static_cast<float>(texspace_pos[1] - coord_floor_y);
float coord_frac_z = static_cast<float>(texspace_pos[2] - coord_floor_z);
assert(coord_frac_x >= 0 && coord_frac_x <= 1);
assert(coord_frac_y >= 0 && coord_frac_y <= 1);
assert(coord_frac_z >= 0 && coord_frac_z <= 1);
coord_floor_x = mod_round_to_neg_inf(coord_floor_x, mottle_texsize);
coord_floor_y = mod_round_to_neg_inf(coord_floor_y, mottle_texsize);
coord_floor_z = mod_round_to_neg_inf(coord_floor_z, mottle_texsize);
assert(coord_floor_x >= 0 && coord_floor_x < mottle_texsize);
assert(coord_floor_y >= 0 && coord_floor_y < mottle_texsize);
assert(coord_floor_z >= 0 && coord_floor_z < mottle_texsize);
// look up sample in the 3d texture at an integer coordinate
auto tex = [](int x, int y, int z) -> uint8_t {
int v;
v = MottlePermutation256[x % 256];
v = MottlePermutation256[(v + y) % 256];
v = MottlePermutation256[(v + z) % 256];
return v;
};
// 3D bilinear interpolation
float res = mix(
mix(
mix(tex(coord_floor_x, coord_floor_y, coord_floor_z),
tex(coord_floor_x + 1, coord_floor_y, coord_floor_z),
coord_frac_x),
mix(tex(coord_floor_x, coord_floor_y + 1, coord_floor_z),
tex(coord_floor_x + 1, coord_floor_y + 1, coord_floor_z),
coord_frac_x),
coord_frac_y),
mix(
mix(tex(coord_floor_x, coord_floor_y, coord_floor_z + 1),
tex(coord_floor_x + 1, coord_floor_y, coord_floor_z + 1),
coord_frac_x),
mix(tex(coord_floor_x, coord_floor_y + 1, coord_floor_z + 1),
tex(coord_floor_x + 1, coord_floor_y + 1, coord_floor_z + 1),
coord_frac_x),
coord_frac_y),
coord_frac_z);
return (res / 255.0f) * 48.0f;
#endif
}
/*
* ============
* LightFace_Min
@ -1425,7 +1529,7 @@ static void LightFace_Min(const mbsp_t *bsp, const mface_t *face, const qvec3d &
sample.color += color * (value / 255.0);
} else {
if (lightsurf->minlightMottle) {
value += rand() % 48;
value += Mottle(lightsurf->points[i]);
}
Light_ClampMin(sample, value, color);
}
@ -1563,6 +1667,23 @@ static void LightFace_PhongDebug(const lightsurf_t *lightsurf, lightmapdict_t *l
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
}
static void LightFace_DebugMottle(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps)
{
/* use a style 0 light map */
lightmap_t *lightmap = Lightmap_ForStyle(lightmaps, 0, lightsurf);
/* Overwrite each point with the mottle noise for that sample... */
for (int i = 0; i < lightsurf->points.size(); i++) {
lightsample_t &sample = lightmap->samples[i];
// mottle is meant to be applied on top of minlight, so add some here
// for preview purposes.
const float minlight = 20.0f;
sample.color = qvec3f(minlight + Mottle(lightsurf->points[i]));
}
Lightmap_Save(lightmaps, lightsurf, lightmap, 0);
}
// mxd. Surface light falloff. Returns color in [0,255]
inline qvec3f SurfaceLight_ColorAtDist(
const settings::worldspawn_keys &cfg, const float &surf_scale, const float &intensity, const qvec3d &color, const float &dist, const float &hotspot_clamp)
@ -1832,6 +1953,7 @@ void SetupDirt(settings::worldspawn_keys &cfg)
/* iterate angle */
float angle = 0.0f;
numDirtVectors = 0;
for (int i = 0; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep) {
/* iterate elevation */
float elevation = elevationStep * 0.5f;
@ -2534,6 +2656,80 @@ static void WriteSingleLightmap(const mbsp_t *bsp, const mface_t *face, const li
}
}
/**
* - Writes (output_width * output_height) bytes to `out`
* - Writes (output_width * output_height * 3) bytes to `lit`
* - Writes (output_width * output_height * 3) bytes to `lux`
*/
static void WriteSingleLightmap_FromDecoupled(const mbsp_t *bsp, const mface_t *face, const lightsurf_t *lightsurf,
const lightmap_t *lm, const int output_width, const int output_height, uint8_t *out, uint8_t *lit, uint8_t *lux)
{
// this is the lightmap data in the "decoupled" coordinate system
std::vector<qvec4f> fullres = LightmapColorsToGLMVector(lightsurf, lm);
// maps a luxel in the vanilla lightmap to the corresponding position in the decoupled lightmap
const qmat4x4f vanillaLMToDecoupled = lightsurf->extents.worldToLMMatrix * lightsurf->vanilla_extents.lmToWorldMatrix;
// samples the "decoupled" lightmap at an integer coordinate, with clamping
auto tex = [&lightsurf, &fullres](int x, int y) -> qvec4f {
const int x_clamped = clamp(x, 0, lightsurf->width - 1);
const int y_clamped = clamp(y, 0, lightsurf->height - 1);
const int sampleindex = (y_clamped * lightsurf->width) + x_clamped;
assert(sampleindex >= 0);
assert(sampleindex < fullres.size());
return fullres[sampleindex];
};
for (int t = 0; t < output_height; t++) {
for (int s = 0; s < output_width; s++) {
// convert from vanilla lm coord to decoupled lm coord
qvec2f decoupled_lm_coord = vanillaLMToDecoupled * qvec4f(s, t, 0, 1);
decoupled_lm_coord = decoupled_lm_coord * light_options.extra.value();
// split into integer/fractional part for bilinear interpolation
const int coord_floor_x = (int)decoupled_lm_coord[0];
const int coord_floor_y = (int)decoupled_lm_coord[1];
const float coord_frac_x = decoupled_lm_coord[0] - coord_floor_x;
const float coord_frac_y = decoupled_lm_coord[1] - coord_floor_y;
// 2D bilinear interpolation
const qvec4f color =
mix(
mix(tex(coord_floor_x, coord_floor_y),
tex(coord_floor_x + 1, coord_floor_y),
coord_frac_x),
mix(tex(coord_floor_x, coord_floor_y + 1),
tex(coord_floor_x + 1, coord_floor_y + 1),
coord_frac_x),
coord_frac_y);
if (lit || out) {
if (lit) {
*lit++ = color[0];
*lit++ = color[1];
*lit++ = color[2];
}
if (out) {
// FIXME: implement
*out++ = 0;
}
}
if (lux) {
// FIXME: implement
*lux++ = 0;
*lux++ = 0;
*lux++ = 0;
}
}
}
}
void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup,
bspx_decoupled_lm_perface *facesup_decoupled, lightsurf_t *lightsurf, const faceextents_t &extents,
const faceextents_t &output_extents)
@ -2726,13 +2922,13 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup,
lightofs = out - filebase.data();
}
if (facesup) {
if (facesup_decoupled) {
facesup_decoupled->offset = lightofs;
face->lightofs = -1;
} else if (facesup) {
facesup->lightofs = lightofs;
} else {
face->lightofs = lightofs;
if (facesup_decoupled) {
facesup_decoupled->offset = lightofs;
}
}
// sanity check that we don't save a lightmap for a non-lightmapped face
@ -2758,6 +2954,37 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup,
lux += (size * 3);
}
}
// write vanilla lightmap if -world_units_per_luxel is in use but not -novanilla
if (facesup_decoupled && !light_options.novanilla.value()) {
// FIXME: duplicates some code from above
GetFileSpace(&out, &lit, &lux, lightsurf->vanilla_extents.numsamples() * numstyles);
// Q2/HL native colored lightmaps
if (bsp->loadversion->game->has_rgb_lightmap) {
lightofs = lit - lit_filebase.data();
} else {
lightofs = out - filebase.data();
}
face->lightofs = lightofs;
for (int mapnum = 0; mapnum < numstyles; mapnum++) {
const lightmap_t *lm = sorted.at(mapnum);
WriteSingleLightmap_FromDecoupled(bsp, face, lightsurf, lm, lightsurf->vanilla_extents.width(),
lightsurf->vanilla_extents.height(), out, lit, lux);
if (out) {
out += lightsurf->vanilla_extents.numsamples();
}
if (lit) {
lit += (lightsurf->vanilla_extents.numsamples() * 3);
}
if (lux) {
lux += (lightsurf->vanilla_extents.numsamples() * 3);
}
}
}
}
std::unique_ptr<lightsurf_t> CreateLightmapSurface(const mbsp_t *bsp, const mface_t *face, const facesup_t *facesup,
@ -2888,6 +3115,9 @@ void DirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings::
if (light_options.debugmode == debugmodes::debugneighbours)
LightFace_DebugNeighbours(&lightsurf, lightmaps);
if (light_options.debugmode == debugmodes::mottle)
LightFace_DebugMottle(&lightsurf, lightmaps);
}
/*
@ -2913,3 +3143,20 @@ void IndirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings
}
}
}
void ResetLtFace()
{
total_light_rays = 0;
total_light_ray_hits = 0;
total_samplepoints = 0;
total_bounce_rays = 0;
total_bounce_ray_hits = 0;
total_surflight_rays = 0;
total_surflight_ray_hits = 0;
fully_transparent_lightmaps = 0;
warned_about_light_map_overflow = false;
warned_about_light_style_overflow = false;
}

View File

@ -182,6 +182,17 @@ static map<int, vector<const mface_t *>> planesToFaces;
static edgeToFaceMap_t EdgeToFaceMap;
static vector<face_cache_t> FaceCache;
void ResetPhong()
{
s_builtPhongCaches = false;
vertex_normals = {};
smoothFaces = {};
vertsToFaces = {};
planesToFaces = {};
EdgeToFaceMap = {};
FaceCache = {};
}
vector<const mface_t *> FacesUsingVert(int vertnum)
{
const auto &vertsToFaces_const = vertsToFaces;

View File

@ -42,6 +42,13 @@ static std::vector<surfacelight_t> surfacelights;
static std::map<int, std::vector<int>> surfacelightsByFacenum;
static size_t total_surflight_points = 0;
void ResetSurflight()
{
surfacelights = {};
surfacelightsByFacenum = {};
total_surflight_points = {};
}
std::vector<surfacelight_t> &GetSurfaceLights()
{
return surfacelights;
@ -54,6 +61,8 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
auto poly = GLM_FacePoints(bsp, face);
const float facearea = qv::PolyArea(poly.begin(), poly.end());
const surfflags_t &extended_flags = extended_texinfo_flags[face->texinfo];
// Avoid small, or zero-area faces
if (facearea < 1)
return;
@ -108,7 +117,7 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
l.omnidirectional = !is_directional;
l.points = std::move(points);
l.style = style;
l.rescale = true;
l.rescale = extended_flags.surflight_rescale;
// Init bbox...
if (light_options.visapprox.value() == visapprox_t::RAYS) {

View File

@ -32,6 +32,30 @@ sceneinfo skygeom; // sky. always occludes.
sceneinfo solidgeom; // solids. always occludes.
sceneinfo filtergeom; // conditional occluders.. needs to run ray intersection filter
static RTCDevice device;
RTCScene scene;
static const mbsp_t *bsp_static;
void ResetEmbree()
{
skygeom = {};
solidgeom = {};
filtergeom = {};
if (scene) {
rtcReleaseScene(scene);
scene = nullptr;
}
if (device) {
rtcReleaseDevice(device);
device = nullptr;
}
bsp_static = nullptr;
}
/**
* Returns 1.0 unless a custom alpha value is set.
* The priority is: "_light_alpha" (read from extended_texinfo_flags), then "alpha", then Q2 surface flags
@ -229,11 +253,6 @@ static void CreateGeometryFromWindings(RTCDevice g_device, RTCScene scene, const
rtcCommitGeometry(geom_1);
}
RTCDevice device;
RTCScene scene;
static const mbsp_t *bsp_static;
void ErrorCallback(void *userptr, const RTCError code, const char *str)
{
fmt::print("RTC Error {}: {}\n", code, str);

View File

@ -779,31 +779,24 @@ static void Brush_LoadEntity(mapentity_t &dst, mapentity_t &src, hull_index_t hu
/* entities in some games never use water merging */
if (!map.is_world_entity(dst) && !qbsp_options.target_game->allow_contented_bmodels) {
contents = qbsp_options.target_game->create_solid_contents();
// bmodels become solid in Q1
/* Hack to turn bmodels with "_mirrorinside" into func_detail_fence in hull 0.
this is to allow "_mirrorinside" to work on func_illusionary, func_wall, etc.
Otherwise they would be CONTENTS_SOLID and the inside faces would be deleted.
It's CONTENTS_DETAIL_FENCE because this gets mapped to CONTENTS_SOLID just
before writing the bsp, and bmodels normally have CONTENTS_SOLID as their
contents type.
*/
if (!hullnum.value_or(0) && contents.is_mirrored(qbsp_options.target_game)) {
contents = qbsp_options.target_game->create_detail_fence_contents(contents);
}
// to allow use of _mirrorinside, we'll set it to detail fence, which will get remapped back
// to CONTENTS_SOLID at export. (we wouldn't generate inside faces if the content was CONTENTS_SOLID
// from the start.)
contents = qbsp_options.target_game->create_detail_fence_contents(qbsp_options.target_game->create_solid_contents());
}
if (hullnum.value_or(0)) {
/* nonsolid brushes don't show up in clipping hulls */
if (!contents.is_any_solid(qbsp_options.target_game) && !contents.is_sky(qbsp_options.target_game)) {
if (!contents.is_any_solid(qbsp_options.target_game)
&& !contents.is_sky(qbsp_options.target_game)
&& !contents.is_fence(qbsp_options.target_game)) {
continue;
}
/* sky brushes are solid in the collision hulls */
if (contents.is_sky(qbsp_options.target_game)) {
contents = qbsp_options.target_game->create_solid_contents();
}
/* all used brushes are solid in the collision hulls */
contents = qbsp_options.target_game->create_solid_contents();
}
// fixme-brushbsp: function calls above can override the values below

View File

@ -576,6 +576,8 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti
flags.no_minlight = true;
if (entity.epairs.get_int("_lightignore") == 1)
flags.light_ignore = true;
if (entity.epairs.has("_surflight_rescale") && entity.epairs.get_int("_surflight_rescale") == 0)
flags.surflight_rescale = false;
// "_minlight_exclude", "_minlight_exclude2", "_minlight_exclude3"...
for (int i = 0; i <= 9; i++) {

View File

@ -770,7 +770,7 @@ void EmitAreaPortals(node_t *headnode)
struct visible_faces_stats_t : logging::stat_tracker_t
{
stat &sides_not_found = register_stat("sides not found", false, true);
stat &sides_not_found = register_stat("sides not found (use -verbose to display)", false, true);
stat &sides_visible = register_stat("sides visible");
};
@ -870,6 +870,7 @@ static void FindPortalSide(portal_t *p, visible_faces_stats_t &stats)
if (!bestside[0] && !bestside[1]) {
stats.sides_not_found++;
logging::print(logging::flag::VERBOSE, "couldn't find portal side at {}\n", p->winding.center());
}
p->sidefound = true;

View File

@ -368,6 +368,9 @@ static void WriteExtendedTexinfoFlags(void)
if (tx.flags.light_ignore) {
t["light_ignore"] = tx.flags.light_ignore;
}
if (tx.flags.surflight_rescale == false) {
t["surflight_rescale"] = tx.flags.surflight_rescale;
}
if (tx.flags.phong_angle) {
t["phong_angle"] = tx.flags.phong_angle;
}

View File

@ -0,0 +1,82 @@
// Game: Quake 2
// Format: Quake2 (Valve)
// entity 0
{
"classname" "worldspawn"
"_tb_textures" "textures/e1u1"
"_bounce" "0"
// brush 0
{
( 928 -1072 880 ) ( 928 -1504 880 ) ( 928 -1504 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 880 ) ( 1120 -1504 880 ) ( 1120 -1504 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 864 ) ( 1120 -1072 864 ) ( 928 -1072 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 880 ) ( 1120 -1072 880 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 864 ) ( 1120 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 880 ) ( 1120 -1072 880 ) ( 1120 -1072 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 1
{
( 928 -1072 1040 ) ( 928 -1504 1040 ) ( 928 -1504 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 1040 ) ( 1120 -1504 1040 ) ( 1120 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1024 ) ( 1120 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 1040 ) ( 1120 -1072 1040 ) ( 1120 -1504 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 1024 ) ( 1120 -1072 1040 ) ( 928 -1072 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1040 ) ( 1120 -1072 1040 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 2
{
( 1120 -1072 1024 ) ( 1120 -1504 1024 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1024 ) ( 1136 -1504 1024 ) ( 1136 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1136 -1504 880 ) ( 1136 -1072 880 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 1024 ) ( 1136 -1072 1024 ) ( 1136 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1136 -1072 880 ) ( 1136 -1072 1024 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1136 -1504 1024 ) ( 1136 -1072 1024 ) ( 1136 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 3
{
( 928 -1072 1024 ) ( 928 -1504 1024 ) ( 928 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 1024 ) ( 944 -1504 1024 ) ( 944 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1504 880 ) ( 944 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 1024 ) ( 944 -1072 1024 ) ( 944 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 944 -1072 880 ) ( 944 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1504 1024 ) ( 944 -1072 1024 ) ( 944 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 4
{
( 944 -1072 1024 ) ( 944 -1088 1024 ) ( 944 -1088 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1088 1024 ) ( 1120 -1088 1024 ) ( 1120 -1088 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1088 880 ) ( 1120 -1072 880 ) ( 944 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 944 -1072 1024 ) ( 1120 -1072 1024 ) ( 1120 -1088 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 880 ) ( 1120 -1072 1024 ) ( 944 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1088 1024 ) ( 1120 -1072 1024 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 5
{
( 936 -1504 1024 ) ( 936 -1520 1024 ) ( 936 -1520 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 936 -1520 1024 ) ( 1112 -1520 1024 ) ( 1112 -1520 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1112 -1520 880 ) ( 1112 -1504 880 ) ( 936 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 936 -1504 1024 ) ( 1112 -1504 1024 ) ( 1112 -1520 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1112 -1504 880 ) ( 1112 -1504 1024 ) ( 936 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1112 -1520 1024 ) ( 1112 -1504 1024 ) ( 1112 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
}
// entity 1
{
"classname" "info_player_start"
"origin" "1040 -1184 904"
"angle" "270"
}
// entity 2
{
"classname" "func_group"
"_surflight_rescale" "0"
// brush 0
{
( 968 -1264 992 ) ( 968 -1464 992 ) ( 968 -1464 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 968 -1464 992 ) ( 1104 -1464 992 ) ( 1104 -1464 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 1104 -1464 904 ) ( 1104 -1264 904 ) ( 968 -1264 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100
( 968 -1264 992 ) ( 1104 -1264 992 ) ( 1104 -1464 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100
( 1104 -1264 904 ) ( 1104 -1264 992 ) ( 968 -1264 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 1104 -1464 992 ) ( 1104 -1264 992 ) ( 1104 -1264 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
}
}

View File

@ -977,3 +977,10 @@
"origin" "-398 -160 94"
"light" "1200"
}
// entity 7
{
"classname" "light"
"origin" "232 -248 104"
"style" "1"
"_color" "183 255 227"
}

View File

@ -0,0 +1,97 @@
// Game: Quake
// Format: Valve
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"wad" "deprecated/free_wad.wad;deprecated/fence.wad;deprecated/origin.wad;deprecated/hintskip.wad"
"_wateralpha" "0.5"
"_tb_def" "builtin:Quake.fgd"
// brush 0
{
( -112 -112 96 ) ( -112 -111 96 ) ( -112 -112 97 ) orangestuff8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 2 2
( -80 -272 80 ) ( -81 -272 80 ) ( -80 -272 81 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -80 -432 80 ) ( -80 -431 80 ) ( -81 -432 80 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 2 2
( -160 -112 96 ) ( -161 -112 96 ) ( -160 -111 96 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 2 2
( -160 0 96 ) ( -160 0 97 ) ( -161 0 96 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( 64 -432 80 ) ( 64 -432 81 ) ( 64 -431 80 ) orangestuff8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 2 2
}
// brush 1
{
( -112 -96 96 ) ( -112 -95 96 ) ( -112 -96 97 ) orangestuff8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 2 2
( -80 0 80 ) ( -81 0 80 ) ( -80 0 81 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -80 -416 80 ) ( -80 -415 80 ) ( -81 -416 80 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 2 2
( -160 -96 224 ) ( -161 -96 224 ) ( -160 -95 224 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 2 2
( -160 16 96 ) ( -160 16 97 ) ( -161 16 96 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( 64 -416 80 ) ( 64 -416 81 ) ( 64 -415 80 ) orangestuff8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 2 2
}
// brush 2
{
( -112 -384 96 ) ( -112 -383 96 ) ( -112 -384 97 ) orangestuff8 [ 0 1 0 8 ] [ 0 0 -1 0 ] 0 2 2
( -80 -288 80 ) ( -81 -288 80 ) ( -80 -288 81 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -80 -704 80 ) ( -80 -703 80 ) ( -81 -704 80 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 -8 ] 180 2 2
( -160 -384 224 ) ( -161 -384 224 ) ( -160 -383 224 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 -8 ] 180 2 2
( -160 -272 96 ) ( -160 -272 97 ) ( -161 -272 96 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( 64 -704 80 ) ( 64 -704 81 ) ( 64 -703 80 ) orangestuff8 [ 0 -1 0 -8 ] [ 0 0 -1 0 ] 0 2 2
}
// brush 3
{
( -128 -112 96 ) ( -128 -111 96 ) ( -128 -112 97 ) orangestuff8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 2 2
( -256 -272 80 ) ( -257 -272 80 ) ( -256 -272 81 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -256 -432 80 ) ( -256 -431 80 ) ( -257 -432 80 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 2 2
( -336 -112 224 ) ( -337 -112 224 ) ( -336 -111 224 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 2 2
( -336 0 96 ) ( -336 0 97 ) ( -337 0 96 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( -112 -432 80 ) ( -112 -432 81 ) ( -112 -431 80 ) orangestuff8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 2 2
}
// brush 4
{
( 64 -112 96 ) ( 64 -111 96 ) ( 64 -112 97 ) orangestuff8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 2 2
( -64 -272 80 ) ( -65 -272 80 ) ( -64 -272 81 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -64 -432 80 ) ( -64 -431 80 ) ( -65 -432 80 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 2 2
( -144 -112 224 ) ( -145 -112 224 ) ( -144 -111 224 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 2 2
( -144 0 96 ) ( -144 0 97 ) ( -145 0 96 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( 80 -432 80 ) ( 80 -432 81 ) ( 80 -431 80 ) orangestuff8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 2 2
}
// brush 5
{
( -112 -112 240 ) ( -112 -111 240 ) ( -112 -112 241 ) orangestuff8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 2 2
( -80 -272 224 ) ( -81 -272 224 ) ( -80 -272 225 ) orangestuff8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 2 2
( -80 -432 224 ) ( -80 -431 224 ) ( -81 -432 224 ) orangestuff8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 2 2
( -160 -112 240 ) ( -161 -112 240 ) ( -160 -111 240 ) orangestuff8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 2 2
( -160 0 240 ) ( -160 0 241 ) ( -161 0 240 ) orangestuff8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 2 2
( 64 -432 224 ) ( 64 -432 225 ) ( 64 -431 224 ) orangestuff8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 2 2
}
}
// entity 1
{
"classname" "info_player_start"
"origin" "-88 -64 120"
}
// entity 2
{
"classname" "func_wall"
"_mirrorinside" "1"
// brush 0
{
( -16 -76 112 ) ( -16 -75 112 ) ( -16 -76 113 ) {trigger [ 0 -1 0 -16 ] [ 0 0 -1 16 ] 0 1 1
( -8 -80 112 ) ( -8 -80 113 ) ( -7 -80 112 ) {trigger [ 1 0 0 16 ] [ 0 0 -1 16 ] 0 1 1
( -8 -76 112 ) ( -7 -76 112 ) ( -8 -75 112 ) {trigger [ -1 0 0 -16 ] [ 0 -1 0 -16 ] 0 1 1
( 56 -28 208 ) ( 56 -27 208 ) ( 57 -28 208 ) {trigger [ 1 0 0 16 ] [ 0 -1 0 -16 ] 0 1 1
( 56 -32 128 ) ( 57 -32 128 ) ( 56 -32 129 ) {trigger [ -1 0 0 -16 ] [ 0 0 -1 16 ] 0 1 1
( 32 -28 128 ) ( 32 -28 129 ) ( 32 -27 128 ) {trigger [ 0 1 0 16 ] [ 0 0 -1 16 ] 0 1 1
}
}
// entity 3
{
"classname" "func_wall"
"_mirrorinside" "1"
// brush 0
{
( -16 -140 112 ) ( -16 -139 112 ) ( -16 -140 113 ) *swater4 [ 0 -1 0 -16 ] [ 0 0 -1 16 ] 0 1 1
( -8 -144 112 ) ( -8 -144 113 ) ( -7 -144 112 ) *swater4 [ 1 0 0 16 ] [ 0 0 -1 16 ] 0 1 1
( -8 -140 112 ) ( -7 -140 112 ) ( -8 -139 112 ) *swater4 [ -1 0 0 -16 ] [ 0 -1 0 -16 ] 0 1 1
( 56 -92 208 ) ( 56 -91 208 ) ( 57 -92 208 ) *swater4 [ 1 0 0 16 ] [ 0 -1 0 -16 ] 0 1 1
( 56 -96 128 ) ( 57 -96 128 ) ( 56 -96 129 ) *swater4 [ -1 0 0 -16 ] [ 0 0 -1 16 ] 0 1 1
( 32 -92 128 ) ( 32 -92 129 ) ( 32 -91 128 ) *swater4 [ 0 1 0 16 ] [ 0 0 -1 16 ] 0 1 1
}
}

View File

@ -188,7 +188,7 @@ TEST_CASE("PolygonCentroid")
const std::initializer_list<qvec3d> poly{{0, 0, 0}, {0, 32, 0}, // colinear
{0, 64, 0}, {64, 64, 0}, {64, 0, 0}};
CHECK(qvec3f(32, 32, 0) == qv::PolyCentroid(poly.begin(), poly.end()));
CHECK(qvec3d(32, 32, 0) == qv::PolyCentroid(poly.begin(), poly.end()));
}
TEST_CASE("PolygonArea")

View File

@ -5,7 +5,12 @@
#include <qbsp/qbsp.hh>
#include <testmaps.hh>
static void LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args)
struct testresults_t {
mbsp_t bsp;
bspxentries_t bspx;
};
static testresults_t LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args)
{
auto map_path = std::filesystem::path(testmaps_dir) / name;
@ -52,9 +57,93 @@ static void LoadTestmap(const std::filesystem::path &name, std::vector<std::stri
// write to .json for inspection
serialize_bsp(bspdata, std::get<mbsp_t>(bspdata.bsp),
fs::path(qbsp_options.bsp_path).replace_extension(".bsp.json"));
return {std::move(std::get<mbsp_t>(bspdata.bsp)),
std::move(bspdata.bspx.entries)};
}
}
TEST_CASE("TestLight") {
LoadTestmap("q2_lightmap_custom_scale.map", {"-threads", "1", "-extra", "-world_units_per_luxel", "8"});
TEST_CASE("-world_units_per_luxel") {
LoadTestmap("q2_lightmap_custom_scale.map", {"-world_units_per_luxel", "8"});
}
TEST_CASE("emissive cube artifacts") {
// A cube with surface flags "light", value "100", placed in a hallway.
//
// Generates harsh lines on the walls/ceiling due to a hack in `light` allowing
// surface lights to emit 50% at 90 degrees off their surface normal (when physically it should be 0%).
//
// It's wanted in some cases (base1.map sewer lights flush with the wall, desired for them to
// emit some lights on to their adjacent wall faces.)
//
// To disable the behaviour in this case with the cube lighting a hallway we have a entity key:
//
// "_surflight_rescale" "0"
//
auto [bsp, bspx] = LoadTestmap("light_q2_emissive_cube.map", {"-threads", "1", "-world_units_per_luxel", "4", "-novanilla"});
const auto start = qvec3d{1044, -1244, 880};
const auto end = qvec3d{1044, -1272, 880};
auto *floor = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], start, {0, 0, 1});
auto lm_info = BSPX_DecoupledLM(bspx, Face_GetNum(&bsp, floor));
const faceextents_t extents(*floor, bsp, lm_info.lmwidth, lm_info.lmheight, lm_info.world_to_lm_space);
// sample the lightmap along the floor, approaching the glowing cube
// should get brighter
qvec3b previous_sample{};
for (int y = start[1]; y >= end[1]; y -= 4) {
qvec3d pos = start;
pos[1] = y;
auto lm_coord = extents.worldToLMCoord(pos);
auto sample = LM_Sample(&bsp, extents, lm_info.offset, lm_coord);
CHECK(sample[0] >= previous_sample[0]);
//logging::print("world: {} lm_coord: {} sample: {} lm size: {}x{}\n", pos, lm_coord, sample, lm_info.lmwidth, lm_info.lmheight);
previous_sample = sample;
}
}
TEST_CASE("-novanilla + -world_units_per_luxel")
{
auto [bsp, bspx] = LoadTestmap("q2_lightmap_custom_scale.map", {"-novanilla", "-world_units_per_luxel", "8"});
for (auto &face : bsp.dfaces) {
CHECK(face.lightofs == -1);
}
// make sure no other bspx lumps are written
CHECK(bspx.size() == 1);
CHECK(bspx.find("DECOUPLED_LM") != bspx.end());
// make sure all dlightdata bytes are accounted for by the DECOUPLED_LM lump
// and no extra was written.
size_t expected_dlightdata_bytes = 0;
for (auto &face : bsp.dfaces) {
// count used styles
size_t face_used_styles = 0;
for (auto style : face.styles) {
if (style != 255) {
++face_used_styles;
}
}
// count used pixels per style
auto lm_info = BSPX_DecoupledLM(bspx, Face_GetNum(&bsp, &face));
const faceextents_t extents(face, bsp, lm_info.lmwidth, lm_info.lmheight, lm_info.world_to_lm_space);
int samples_per_face = extents.numsamples() * face_used_styles;
// round up to multiple of 4
if (samples_per_face % 4) {
samples_per_face += (4 - (samples_per_face % 4));
}
int bytes_per_face = 3 * samples_per_face;
expected_dlightdata_bytes += bytes_per_face;
}
CHECK(bsp.dlightdata.size() == expected_dlightdata_bytes);
}

View File

@ -822,6 +822,29 @@ TEST_CASE("water_detail_illusionary" * doctest::test_suite("testmaps_q1"))
}
}
TEST_CASE("qbsp_bmodel_mirrorinside_with_liquid" * doctest::test_suite("testmaps_q1"))
{
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_bmodel_mirrorinside_with_liquid.map");
REQUIRE(prt.has_value());
const qvec3d model1_fenceface{-16, -56, 168};
const qvec3d model2_waterface{-16, -120, 168};
CHECK(2 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[1], model1_fenceface).size());
CHECK(2 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[2], model2_waterface).size());
// both bmodels should be CONTENTS_SOLID in all hulls
for (int model_idx = 1; model_idx <= 2; ++model_idx) {
for (int hull = 0; hull <= 2; ++hull) {
auto &model = bsp.dmodels[model_idx];
INFO("model: ", model_idx, " hull: ", hull);
CHECK(CONTENTS_SOLID == BSP_FindContentsAtPoint(&bsp, {hull}, &model, (model.mins + model.maxs) / 2));
}
}
}
TEST_CASE("noclipfaces" * doctest::test_suite("testmaps_q1"))
{
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_noclipfaces.map");
@ -1123,7 +1146,7 @@ TEST_CASE("q1_cube")
REQUIRE_FALSE(prt.has_value());
const aabb3d cube_bounds {
const aabb3f cube_bounds {
{32, -240, 80},
{80, -144, 112}
};
@ -1134,8 +1157,8 @@ TEST_CASE("q1_cube")
// check the solid leaf
auto& solid_leaf = bsp.dleafs[0];
CHECK(solid_leaf.mins == qvec3d(0,0,0));
CHECK(solid_leaf.maxs == qvec3d(0,0,0));
CHECK(solid_leaf.mins == qvec3f(0,0,0));
CHECK(solid_leaf.maxs == qvec3f(0,0,0));
// check the empty leafs
for (int i = 1; i < 7; ++i) {
@ -1185,7 +1208,7 @@ TEST_CASE("q1_clip_func_wall" * doctest::test_suite("testmaps_q1"))
REQUIRE(prt.has_value());
const aabb3d cube_bounds {
const aabb3f cube_bounds {
{64, 64, 48},
{128, 128, 80}
};

View File

@ -492,8 +492,8 @@ TEST_CASE("q2_door" * doctest::test_suite("testmaps_q2")) {
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
const aabb3d world_tight_bounds {{-64, -64, -16}, {64, 80, 128}};
const aabb3d bmodel_tight_bounds {{-48, 48, 16}, {48, 64, 112}};
const aabb3f world_tight_bounds {{-64, -64, -16}, {64, 80, 128}};
const aabb3f bmodel_tight_bounds {{-48, 48, 16}, {48, 64, 112}};
CHECK(world_tight_bounds.mins() == bsp.dmodels[0].mins);
CHECK(world_tight_bounds.maxs() == bsp.dmodels[0].maxs);