Merge branch 'brushbsp' of https://github.com/ericwa/ericw-tools into brushbsp
This commit is contained in:
commit
f51ab9b75e
|
|
@ -9,8 +9,11 @@ jobs:
|
|||
# Don't cancel the macOS build if the Linux build fails, etc.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-11]
|
||||
os: [ubuntu-20.04, macos-11, windows-2019]
|
||||
use-asan: [YES, NO]
|
||||
exclude:
|
||||
- os: windows-2019
|
||||
use-asan: YES
|
||||
env:
|
||||
# Expose to the build-*.sh in an environment variable
|
||||
USE_ASAN: ${{ matrix.use-asan }}
|
||||
|
|
@ -19,6 +22,11 @@ jobs:
|
|||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
# https://github.com/ilammy/msvc-dev-cmd
|
||||
- name: Setup MSVC environment
|
||||
if: runner.os == 'Windows'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Linux Build
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
|
|
@ -31,3 +39,48 @@ jobs:
|
|||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
./build-osx.sh
|
||||
|
||||
- name: Windows Build
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
.\build-windows.ps1
|
||||
|
||||
# Upload artifacts.
|
||||
# These need to be separate, otherwise all of the artifacts are bundled into
|
||||
# one .zip file.
|
||||
|
||||
- name: Upload win64 artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ runner.os == 'Windows' && matrix.use-asan == 'NO' }}
|
||||
with:
|
||||
name: ericw-tools-${{ github.sha }}-win64
|
||||
path: |
|
||||
build-windows/ericw-tools-*.zip
|
||||
|
||||
- name: Upload Linux artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ runner.os == 'Linux' && matrix.use-asan == 'NO' }}
|
||||
with:
|
||||
name: ericw-tools-${{ github.sha }}-Linux
|
||||
path: |
|
||||
build-linux/ericw-tools-*.zip
|
||||
|
||||
- name: Upload macOS artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ runner.os == 'macOS' && matrix.use-asan == 'NO' }}
|
||||
with:
|
||||
name: ericw-tools-${{ github.sha }}-macOS
|
||||
path: |
|
||||
build-osx/ericw-tools-*.zip
|
||||
|
||||
- name: Create GitHub Release and upload builds
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.use-asan == 'NO' }}
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
build-osx/ericw-tools-*.zip
|
||||
build-linux/ericw-tools-*.zip
|
||||
build-windows/ericw-tools-*.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ testmaps/*.bsp
|
|||
testmaps/*.bsp.qbsplog
|
||||
testmaps/*.log
|
||||
testmaps/*.prt
|
||||
testmaps/*.obj
|
||||
testmaps/*.pts
|
||||
testmaps/*.vis
|
||||
testmaps/*.texinfo
|
||||
|
|
|
|||
|
|
@ -13,12 +13,3 @@ build_script:
|
|||
- cmd: powershell .\build-appveyor.ps1
|
||||
artifacts:
|
||||
- path: cmakebuild\*.zip
|
||||
deploy:
|
||||
description: 'release description'
|
||||
provider: GitHub
|
||||
auth_token:
|
||||
secure: 'kTa/cPIBtiixoSjXq1WoVD04ZFzbGhTPcPChAkh99Kf5Sqhy+kE8E3jUYe28nPDO'
|
||||
draft: true
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ $env:Path += ";C:\Program Files\Git\usr\bin"
|
|||
py -m venv ericwtools-env
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
ericwtools-env\Scripts\Activate.ps1
|
||||
python.exe -m pip install sphinx_rtd_theme
|
||||
python.exe -m pip install -r docs/requirements.txt --force-reinstall
|
||||
|
||||
# Confirm Sphinx is installed
|
||||
get-command sphinx-build
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
python3 -m pip install sphinx_rtd_theme
|
||||
python3 -m pip install -r docs/requirements.txt --force-reinstall
|
||||
export PATH="~/.local/bin/:$PATH"
|
||||
|
||||
BUILD_DIR=build-linux
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# for sha256sum, used by the tests
|
||||
brew install coreutils
|
||||
|
||||
python3 -m pip install sphinx_rtd_theme
|
||||
python3 -m pip install -r docs/requirements.txt --force-reinstall
|
||||
|
||||
BUILD_DIR=build-osx
|
||||
EMBREE_ZIP="https://github.com/embree/embree/releases/download/v3.13.0/embree-3.13.0.x86_64.macosx.zip"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# Download embree and tbb
|
||||
Invoke-WebRequest 'https://github.com/embree/embree/releases/download/v3.12.1/embree-3.12.1.x64.vc14.windows.zip' -OutFile 'embree64.zip'
|
||||
7z x embree64.zip -oc:\
|
||||
Invoke-WebRequest 'https://github.com/oneapi-src/oneTBB/releases/download/v2020.2/tbb-2020.2-win.zip' -OutFile 'tbb.zip'
|
||||
7z x tbb.zip -oc:\
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
# Create and activate a Python virtual environment, and install sphinx (for building our documentation)
|
||||
py -m venv ericwtools-env
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
ericwtools-env\Scripts\Activate.ps1
|
||||
python.exe -m pip install -r docs/requirements.txt --force-reinstall
|
||||
|
||||
# Confirm Sphinx is installed
|
||||
get-command sphinx-build
|
||||
|
||||
choco install ninja
|
||||
|
||||
mkdir build-windows
|
||||
cd build-windows
|
||||
|
||||
cmake .. -GNinja -Dembree_DIR="C:\embree-3.12.1.x64.vc14.windows" -DTBB_DIR="C:\tbb\cmake" -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
ninja
|
||||
if ( $? -eq $false ) {
|
||||
throw "build failed"
|
||||
}
|
||||
|
||||
cpack
|
||||
if ( $? -eq $false ) {
|
||||
throw "package failed"
|
||||
}
|
||||
|
||||
.\tests\tests.exe
|
||||
|
||||
if ( $? -eq $false ) {
|
||||
throw "tests failed"
|
||||
}
|
||||
|
||||
# run hidden tests (releaseonly)
|
||||
.\tests\tests.exe [.]
|
||||
|
||||
if ( $? -eq $false ) {
|
||||
throw "tests [.] failed"
|
||||
}
|
||||
|
|
@ -39,3 +39,16 @@ add_library(common STATIC
|
|||
)
|
||||
|
||||
target_link_libraries(common ${CMAKE_THREAD_LIBS_INIT} TBB::tbb TBB::tbbmalloc fmt::fmt nlohmann_json::nlohmann_json)
|
||||
|
||||
target_precompile_headers(common INTERFACE
|
||||
<filesystem>
|
||||
<array>
|
||||
<list>
|
||||
<vector>
|
||||
<set>
|
||||
<unordered_set>
|
||||
<map>
|
||||
<unordered_map>
|
||||
<memory>
|
||||
<variant>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#include <cstdint>
|
||||
#include <limits.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
#include <utility>
|
||||
#include <tuple>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "tbb/parallel_for.h"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include <common/qvec.hh>
|
||||
|
||||
#include <cmath> // for NAN
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
/*
|
||||
* SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
sphinx==5.1.1
|
||||
sphinx_rtd_theme==1.0.0
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <array>
|
||||
|
|
@ -271,7 +271,9 @@ enum class plane_type_t
|
|||
};
|
||||
|
||||
// Fmt support
|
||||
template <> struct fmt::formatter<plane_type_t> : formatter<string_view> {
|
||||
template <> struct fmt::formatter<plane_type_t> {
|
||||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(plane_type_t t, FormatContext& ctx) {
|
||||
string_view name = "unknown";
|
||||
|
|
@ -284,7 +286,7 @@ template <> struct fmt::formatter<plane_type_t> : formatter<string_view> {
|
|||
case plane_type_t::PLANE_ANYY: name = "PLANE_ANYY"; break;
|
||||
case plane_type_t::PLANE_ANYZ: name = "PLANE_ANYZ"; break;
|
||||
}
|
||||
return formatter<string_view>::format(name, ctx);
|
||||
return format_to(ctx.out(), "{}", name);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <algorithm> // for std::min
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
|
@ -27,11 +28,13 @@
|
|||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <cstdarg>
|
||||
#include <cstring> // for memcpy()
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <fmt/format.h>
|
||||
#include <common/log.hh>
|
||||
#include <tuple> // for std::apply()
|
||||
|
||||
#if defined(__has_include) && __has_include(<strings.h>)
|
||||
#include <strings.h>
|
||||
|
|
|
|||
|
|
@ -131,15 +131,17 @@ inline fs::path DefaultExtension(const fs::path &path, const fs::path &extension
|
|||
return fs::path(path).replace_extension(extension);
|
||||
}
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
// TODO: no wchar_t support in this version apparently
|
||||
template<>
|
||||
struct fmt::formatter<fs::path> : formatter<std::string>
|
||||
struct fmt::formatter<fs::path>
|
||||
{
|
||||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); }
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(const fs::path &p, FormatContext &ctx)
|
||||
{
|
||||
return formatter<std::string>::format(p.string(), ctx);
|
||||
return format_to(ctx.out(), "{}", p.string());
|
||||
}
|
||||
};
|
||||
|
|
@ -30,7 +30,8 @@
|
|||
#include <cstdarg>
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <fmt/format.h>
|
||||
#include <cmath> // for log10
|
||||
#include <fmt/core.h>
|
||||
#include <common/bitflags.hh>
|
||||
#include <common/fs.hh>
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
#include <tuple>
|
||||
#include "common/mathlib.hh"
|
||||
#include "common/cmdlib.hh"
|
||||
|
|
@ -340,17 +340,19 @@ public:
|
|||
|
||||
// Fmt support
|
||||
template<class T, size_t N>
|
||||
struct fmt::formatter<qvec<T, N>> : formatter<T>
|
||||
struct fmt::formatter<qvec<T, N>>
|
||||
{
|
||||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); }
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(const qvec<T, N> &p, FormatContext &ctx) -> decltype(ctx.out())
|
||||
{
|
||||
for (size_t i = 0; i < N - 1; i++) {
|
||||
formatter<T>::format(p[i], ctx);
|
||||
format_to(ctx.out(), "{}", p[i]);
|
||||
format_to(ctx.out(), " ");
|
||||
}
|
||||
|
||||
return formatter<T>::format(p[N - 1], ctx);
|
||||
return format_to(ctx.out(), "{}", p[N - 1]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ See file, 'COPYING', for details.
|
|||
#include <QWheelEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QTime>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
GLView::GLView(QWidget *parent)
|
||||
: QOpenGLWidget(parent), m_keysPressed(0), m_keymoveUpdateTimer(0), m_lastMouseDownPos(0, 0), m_displayAspect(1),
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ static void LeafNode(node_t *leafnode, bspbrush_t::container brushes, bspstats_t
|
|||
|
||||
qbsp_options.target_game->count_contents_in_stats(leafnode->contents, *stats.leafstats);
|
||||
|
||||
if (qbsp_options.debugleak.value()) {
|
||||
if (qbsp_options.debugleak.value() || qbsp_options.debugbspbrushes.value()) {
|
||||
leafnode->bsp_brushes = brushes;
|
||||
} else {
|
||||
leafnode->volume.reset();
|
||||
|
|
|
|||
|
|
@ -3348,6 +3348,9 @@ void WriteBspBrushMap(std::string_view filename_suffix, const bspbrush_t::contai
|
|||
fmt::print(f, "{{\n\"classname\" \"worldspawn\"\n");
|
||||
|
||||
for (auto &brush : list) {
|
||||
if (!brush) {
|
||||
continue;
|
||||
}
|
||||
fmt::print(f, "{{\n");
|
||||
for (auto &face : brush->sides) {
|
||||
winding_t w = BaseWindingForPlane<winding_t>(face.get_plane());
|
||||
|
|
|
|||
|
|
@ -438,7 +438,9 @@ static void GatherBspbrushes_r(node_t *node, bspbrush_t::container &container)
|
|||
static void GatherLeafVolumes_r(node_t *node, bspbrush_t::container &container)
|
||||
{
|
||||
if (node->is_leaf) {
|
||||
container.push_back(node->volume);
|
||||
if (!node->contents.is_empty(qbsp_options.target_game)) {
|
||||
container.push_back(node->volume);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
const char *testmaps_dir = "@CMAKE_SOURCE_DIR@/testmaps";
|
||||
const char *test_quake_maps_dir = "@TEST_QUAKE_MAP_EXPORT_DIR@";
|
||||
const char *test_quake2_maps_dir = "@TEST_QUAKE2_MAP_EXPORT_DIR@";
|
||||
inline const char *testmaps_dir = "@CMAKE_SOURCE_DIR@/testmaps";
|
||||
inline const char *test_quake_maps_dir = "@TEST_QUAKE_MAP_EXPORT_DIR@";
|
||||
inline const char *test_quake2_maps_dir = "@TEST_QUAKE2_MAP_EXPORT_DIR@";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,8 @@ add_executable(tests
|
|||
test_light.cc
|
||||
test_ltface.cc
|
||||
test_qbsp.cc
|
||||
test_qbsp.hh
|
||||
test_qbsp_q2.cc
|
||||
test_vis.cc
|
||||
${CMAKE_BINARY_DIR}/testmaps.hh
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,25 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
|
||||
#include "common/settings.hh"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/reporters/catch_reporter_event_listener.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
|
||||
class TestRunListener : public Catch::EventListenerBase {
|
||||
public:
|
||||
using Catch::EventListenerBase::EventListenerBase;
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const&) override {
|
||||
// writing console colors within test case output breaks Catch2/CLion integration
|
||||
logging::enable_color_codes = false;
|
||||
}
|
||||
};
|
||||
|
||||
CATCH_REGISTER_LISTENER(TestRunListener)
|
||||
|
||||
// test booleans
|
||||
TEST_CASE("booleanFlagImplicit", "[settings]")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <light/entities.hh>
|
||||
#include <vector>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
|
||||
#include <light/light.hh>
|
||||
#include <light/entities.hh>
|
||||
|
||||
#include <random>
|
||||
#include <algorithm> // for std::sort
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <common/qvec.hh>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <light/ltface.hh>
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
#include "test_qbsp.hh"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_vector.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <qbsp/brush.hh>
|
||||
#include <qbsp/brushbsp.hh>
|
||||
|
|
@ -23,7 +27,7 @@
|
|||
|
||||
// FIXME: Clear global data (planes, etc) between each test
|
||||
|
||||
static const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t &brush, const std::string &texname)
|
||||
const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t &brush, const std::string &texname)
|
||||
{
|
||||
for (auto &face : brush.faces) {
|
||||
if (face.texname == texname) {
|
||||
|
|
@ -33,7 +37,7 @@ static const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t &brus
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static mapentity_t &LoadMap(const char *map)
|
||||
mapentity_t &LoadMap(const char *map)
|
||||
{
|
||||
qbsp_options.target_version = &bspver_q1;
|
||||
qbsp_options.target_game = qbsp_options.target_version->game;
|
||||
|
|
@ -56,7 +60,7 @@ static mapentity_t &LoadMap(const char *map)
|
|||
#include <common/bspinfo.hh>
|
||||
|
||||
#if 0
|
||||
static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRef(const std::filesystem::path &name)
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRef(const std::filesystem::path &name)
|
||||
{
|
||||
const char *destdir = test_quake2_maps_dir;
|
||||
if (strlen(destdir) == 0) {
|
||||
|
|
@ -116,7 +120,7 @@ static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRe
|
|||
std::move(prtfile));
|
||||
}
|
||||
|
||||
static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRefQ1(const std::filesystem::path &name)
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRefQ1(const std::filesystem::path &name)
|
||||
{
|
||||
auto testmap_path = std::filesystem::path(testmaps_dir) / name;
|
||||
std::string testmap_path_string = testmap_path.generic_string();
|
||||
|
|
@ -169,7 +173,7 @@ static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapRe
|
|||
}
|
||||
#endif
|
||||
|
||||
static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args = {})
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args)
|
||||
{
|
||||
auto map_path = std::filesystem::path(testmaps_dir) / name;
|
||||
auto bsp_path = map_path;
|
||||
|
|
@ -251,7 +255,7 @@ static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmap(c
|
|||
std::move(prtfile));
|
||||
}
|
||||
|
||||
static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ2(const std::filesystem::path &name, std::vector<std::string> extra_args = {})
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ2(const std::filesystem::path &name, std::vector<std::string> extra_args)
|
||||
{
|
||||
#if 0
|
||||
return LoadTestmapRef(name);
|
||||
|
|
@ -261,7 +265,7 @@ static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ2
|
|||
#endif
|
||||
}
|
||||
|
||||
static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ1(const std::filesystem::path &name, std::vector<std::string> extra_args = {})
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ1(const std::filesystem::path &name, std::vector<std::string> extra_args)
|
||||
{
|
||||
#if 0
|
||||
return LoadTestmapRefQ1(name);
|
||||
|
|
@ -270,7 +274,7 @@ static std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ1
|
|||
#endif
|
||||
}
|
||||
|
||||
static void CheckFilled(const mbsp_t &bsp, hull_index_t hullnum)
|
||||
void CheckFilled(const mbsp_t &bsp, hull_index_t hullnum)
|
||||
{
|
||||
int32_t contents = BSP_FindContentsAtPoint(&bsp, hullnum, &bsp.dmodels[0], qvec3d{8192, 8192, 8192});
|
||||
|
||||
|
|
@ -282,7 +286,7 @@ static void CheckFilled(const mbsp_t &bsp, hull_index_t hullnum)
|
|||
}
|
||||
|
||||
|
||||
static void CheckFilled(const mbsp_t &bsp)
|
||||
void CheckFilled(const mbsp_t &bsp)
|
||||
{
|
||||
if (bsp.loadversion->game->id == GAME_QUAKE_II) {
|
||||
CheckFilled(bsp, 0);
|
||||
|
|
@ -295,7 +299,7 @@ static void CheckFilled(const mbsp_t &bsp)
|
|||
}
|
||||
|
||||
#if 0
|
||||
static mbsp_t LoadBsp(const std::filesystem::path &path_in)
|
||||
mbsp_t LoadBsp(const std::filesystem::path &path_in)
|
||||
{
|
||||
std::filesystem::path path = path_in;
|
||||
|
||||
|
|
@ -308,7 +312,7 @@ static mbsp_t LoadBsp(const std::filesystem::path &path_in)
|
|||
}
|
||||
#endif
|
||||
|
||||
static std::map<std::string, std::vector<const mface_t *>> MakeTextureToFaceMap(const mbsp_t &bsp)
|
||||
std::map<std::string, std::vector<const mface_t *>> MakeTextureToFaceMap(const mbsp_t &bsp)
|
||||
{
|
||||
std::map<std::string, std::vector<const mface_t *>> result;
|
||||
|
||||
|
|
@ -319,7 +323,7 @@ static std::map<std::string, std::vector<const mface_t *>> MakeTextureToFaceMap(
|
|||
return result;
|
||||
}
|
||||
|
||||
static const texvecf &GetTexvecs(const char *map, const char *texname)
|
||||
const texvecf &GetTexvecs(const char *map, const char *texname)
|
||||
{
|
||||
mapentity_t &worldspawn = LoadMap(map);
|
||||
|
||||
|
|
@ -330,7 +334,7 @@ static const texvecf &GetTexvecs(const char *map, const char *texname)
|
|||
return mapface->get_texvecs();
|
||||
}
|
||||
|
||||
static std::vector<std::string> TexNames(const mbsp_t &bsp, std::vector<const mface_t *> faces)
|
||||
std::vector<std::string> TexNames(const mbsp_t &bsp, std::vector<const mface_t *> faces)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (auto &face : faces) {
|
||||
|
|
@ -339,7 +343,7 @@ static std::vector<std::string> TexNames(const mbsp_t &bsp, std::vector<const mf
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::vector<const mface_t *> FacesWithTextureName(const mbsp_t &bsp, const std::string &name)
|
||||
std::vector<const mface_t *> FacesWithTextureName(const mbsp_t &bsp, const std::string &name)
|
||||
{
|
||||
std::vector<const mface_t *> result;
|
||||
for (auto &face : bsp.dfaces) {
|
||||
|
|
@ -350,9 +354,6 @@ static std::vector<const mface_t *> FacesWithTextureName(const mbsp_t &bsp, cons
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::map<int, int> CountClipnodeLeafsByContentType(const mbsp_t& bsp, int hullnum);
|
||||
static int CountClipnodeNodes(const mbsp_t& bsp, int hullnum);
|
||||
|
||||
// https://github.com/ericwa/ericw-tools/issues/158
|
||||
TEST_CASE("testTextureIssue", "[qbsp]")
|
||||
{
|
||||
|
|
@ -564,28 +565,33 @@ TEST_CASE("chop_no_change", "[testmaps_q1]")
|
|||
|
||||
TEST_CASE("simple_sealed", "[testmaps_q1]")
|
||||
{
|
||||
auto mapname = GENERATE("qbsp_simple_sealed.map", "qbsp_simple_sealed_rotated.map");
|
||||
const std::vector<std::string> quake_maps{"qbsp_simple_sealed.map", "qbsp_simple_sealed_rotated.map"};
|
||||
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ1(mapname);
|
||||
for (const auto& mapname : quake_maps) {
|
||||
DYNAMIC_SECTION("testing " << mapname) {
|
||||
|
||||
REQUIRE(bsp.dleafs.size() == 2);
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ1(mapname);
|
||||
|
||||
REQUIRE(bsp.dleafs[0].contents == CONTENTS_SOLID);
|
||||
REQUIRE(bsp.dleafs[1].contents == CONTENTS_EMPTY);
|
||||
|
||||
// just a hollow box
|
||||
REQUIRE(bsp.dfaces.size() == 6);
|
||||
REQUIRE(bsp.dleafs.size() == 2);
|
||||
|
||||
// no bspx lumps
|
||||
CHECK(bspx.empty());
|
||||
REQUIRE(bsp.dleafs[0].contents == CONTENTS_SOLID);
|
||||
REQUIRE(bsp.dleafs[1].contents == CONTENTS_EMPTY);
|
||||
|
||||
// check markfaces
|
||||
CHECK(bsp.dleafs[0].nummarksurfaces == 0);
|
||||
CHECK(bsp.dleafs[0].firstmarksurface == 0);
|
||||
// just a hollow box
|
||||
REQUIRE(bsp.dfaces.size() == 6);
|
||||
|
||||
CHECK(bsp.dleafs[1].nummarksurfaces == 6);
|
||||
CHECK(bsp.dleafs[1].firstmarksurface == 0);
|
||||
CHECK_THAT(bsp.dleaffaces, Catch::Matchers::UnorderedEquals(std::vector<uint32_t>{0,1,2,3,4,5}));
|
||||
// no bspx lumps
|
||||
CHECK(bspx.empty());
|
||||
|
||||
// check markfaces
|
||||
CHECK(bsp.dleafs[0].nummarksurfaces == 0);
|
||||
CHECK(bsp.dleafs[0].firstmarksurface == 0);
|
||||
|
||||
CHECK(bsp.dleafs[1].nummarksurfaces == 6);
|
||||
CHECK(bsp.dleafs[1].firstmarksurface == 0);
|
||||
CHECK_THAT(bsp.dleaffaces, Catch::Matchers::UnorderedEquals(std::vector<uint32_t>{0, 1, 2, 3, 4, 5}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("simple_sealed2", "[testmaps_q1]")
|
||||
|
|
@ -1266,272 +1272,6 @@ TEST_CASE("qbsp_q1_0125unit_faces", "[testmaps_q1][!mayfail]")
|
|||
CHECK(2 == bsp.dfaces.size());
|
||||
}
|
||||
|
||||
// q2 testmaps
|
||||
|
||||
TEST_CASE("detail", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_detail.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// stats
|
||||
CHECK(1 == bsp.dmodels.size());
|
||||
// Q2 reserves leaf 0 as an invalid leaf
|
||||
|
||||
// leafs:
|
||||
// 6 solid leafs outside the room (* can be more depending on when the "divider" is cut)
|
||||
// 1 empty leaf filling the room above the divider
|
||||
// 2 empty leafs + 1 solid leaf for divider
|
||||
// 1 detail leaf for button
|
||||
// 4 empty leafs around + 1 on top of button
|
||||
|
||||
std::map<int32_t, int> counts_by_contents;
|
||||
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
|
||||
++counts_by_contents[bsp.dleafs[i].contents];
|
||||
}
|
||||
CHECK(2 == counts_by_contents.size()); // number of types
|
||||
|
||||
|
||||
CHECK(counts_by_contents.find(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == counts_by_contents.end()); // the detail bit gets cleared
|
||||
CHECK(8 == counts_by_contents.at(0)); // empty leafs
|
||||
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) >= 8);
|
||||
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) <= 12);
|
||||
|
||||
// clusters:
|
||||
// 1 empty cluster filling the room above the divider
|
||||
// 2 empty clusters created by divider
|
||||
// 1 cluster for the part of the room with the button
|
||||
|
||||
std::set<int> clusters;
|
||||
// first add the empty leafs
|
||||
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
|
||||
if (0 == bsp.dleafs[i].contents) {
|
||||
clusters.insert(bsp.dleafs[i].cluster);
|
||||
}
|
||||
}
|
||||
CHECK(4 == clusters.size());
|
||||
|
||||
// various points in the main room cluster
|
||||
const qvec3d under_button{246, 436, 96}; // directly on the main floor plane
|
||||
const qvec3d inside_button{246, 436, 98};
|
||||
const qvec3d above_button{246, 436, 120};
|
||||
const qvec3d beside_button{246, 400, 100}; // should be a different empty leaf than above_button, but same cluster
|
||||
|
||||
// side room (different cluster)
|
||||
const qvec3d side_room{138, 576, 140};
|
||||
|
||||
// detail clips away world faces
|
||||
CHECK(nullptr == BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], under_button, {0, 0, 1}));
|
||||
|
||||
// check for correct contents
|
||||
auto *detail_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_button);
|
||||
CHECK(Q2_CONTENTS_SOLID == detail_leaf->contents);
|
||||
CHECK(-1 == detail_leaf->cluster);
|
||||
|
||||
// check for button (detail) brush
|
||||
CHECK(1 == Leaf_Brushes(&bsp, detail_leaf).size());
|
||||
CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) ==
|
||||
Leaf_Brushes(&bsp, detail_leaf).at(0)->contents);
|
||||
|
||||
// get more leafs
|
||||
auto *empty_leaf_above_button = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], above_button);
|
||||
CHECK(0 == empty_leaf_above_button->contents);
|
||||
CHECK(0 == Leaf_Brushes(&bsp, empty_leaf_above_button).size());
|
||||
|
||||
auto *empty_leaf_side_room = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], side_room);
|
||||
CHECK(0 == empty_leaf_side_room->contents);
|
||||
CHECK(0 == Leaf_Brushes(&bsp, empty_leaf_side_room).size());
|
||||
CHECK(empty_leaf_side_room->cluster != empty_leaf_above_button->cluster);
|
||||
|
||||
auto *empty_leaf_beside_button = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], beside_button);
|
||||
CHECK(0 == empty_leaf_beside_button->contents);
|
||||
CHECK(-1 != empty_leaf_beside_button->cluster);
|
||||
CHECK(empty_leaf_above_button->cluster == empty_leaf_beside_button->cluster);
|
||||
CHECK(empty_leaf_above_button != empty_leaf_beside_button);
|
||||
|
||||
CHECK(prt->portals.size() == 5);
|
||||
CHECK(prt->portalleafs_real == 0); // not used by Q2
|
||||
CHECK(prt->portalleafs == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("playerclip", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_playerclip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_playerclip{32, -136, 144};
|
||||
auto *playerclip_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_playerclip);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_DETAIL) == playerclip_leaf->contents);
|
||||
|
||||
// make sure faces at these locations aren't clipped away
|
||||
const qvec3d floor_under_clip{32, -136, 96};
|
||||
const qvec3d pillar_side_in_clip1{32, -48, 144};
|
||||
const qvec3d pillar_side_in_clip2{32, -208, 144};
|
||||
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_under_clip, {0, 0, 1}));
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], pillar_side_in_clip1, {0, -1, 0}));
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], pillar_side_in_clip2, {0, 1, 0}));
|
||||
|
||||
// make sure no face is generated for the playerclip brush
|
||||
const qvec3d playerclip_front_face{16, -152, 144};
|
||||
CHECK(nullptr == BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], playerclip_front_face, {-1, 0, 0}));
|
||||
|
||||
// check for brush
|
||||
CHECK(1 == Leaf_Brushes(&bsp, playerclip_leaf).size());
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_DETAIL) == Leaf_Brushes(&bsp, playerclip_leaf).at(0)->contents);
|
||||
}
|
||||
|
||||
TEST_CASE("areaportal", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_areaportal.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// area 0 is a placeholder
|
||||
// areaportal 0 is a placeholder
|
||||
//
|
||||
// the conceptual area portal has portalnum 1, and consists of two dareaportals entries with connections to area 1 and 2
|
||||
CHECK_THAT(bsp.dareaportals, Catch::Matchers::UnorderedEquals(std::vector<dareaportal_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
CHECK_THAT(bsp.dareas, Catch::Matchers::UnorderedEquals(std::vector<darea_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
|
||||
// look up the leafs
|
||||
const qvec3d player_start{-88, -112, 120};
|
||||
const qvec3d other_room{128, -112, 120};
|
||||
const qvec3d areaportal_pos{32, -112, 120};
|
||||
const qvec3d void_pos{-408, -112, 120};
|
||||
|
||||
auto *player_start_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_start);
|
||||
auto *other_room_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], other_room);
|
||||
auto *areaportal_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], areaportal_pos);
|
||||
auto *void_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], void_pos);
|
||||
|
||||
// check leaf contents
|
||||
CHECK(0 == player_start_leaf->contents);
|
||||
CHECK(0 == other_room_leaf->contents);
|
||||
CHECK(Q2_CONTENTS_AREAPORTAL == areaportal_leaf->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == void_leaf->contents);
|
||||
|
||||
// make sure faces at these locations aren't clipped away
|
||||
const qvec3d floor_under_areaportal{32, -136, 96};
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_under_areaportal, {0, 0, 1}));
|
||||
|
||||
// check for brushes
|
||||
CHECK(1 == Leaf_Brushes(&bsp, areaportal_leaf).size());
|
||||
CHECK(Q2_CONTENTS_AREAPORTAL == Leaf_Brushes(&bsp, areaportal_leaf).at(0)->contents);
|
||||
|
||||
CHECK(1 == Leaf_Brushes(&bsp, void_leaf).size());
|
||||
CHECK(Q2_CONTENTS_SOLID == Leaf_Brushes(&bsp, void_leaf).at(0)->contents);
|
||||
|
||||
// check leaf areas
|
||||
CHECK_THAT((std::vector<int32_t>{1, 2}), Catch::Matchers::UnorderedEquals(std::vector<int32_t>{player_start_leaf->area, other_room_leaf->area}));
|
||||
// the areaportal leaf itself actually gets assigned to one of the two sides' areas
|
||||
CHECK((areaportal_leaf->area == 1 || areaportal_leaf->area == 2));
|
||||
CHECK(0 == void_leaf->area); // a solid leaf gets the invalid area
|
||||
|
||||
// check the func_areaportal entity had its "style" set
|
||||
parser_t parser(bsp.dentdata, { "qbsp_q2_areaportal.bsp" });
|
||||
auto ents = EntData_Parse(parser);
|
||||
auto it = std::find_if(ents.begin(), ents.end(),
|
||||
[](const entdict_t &dict) { return dict.get("classname") == "func_areaportal"; });
|
||||
|
||||
REQUIRE(it != ents.end());
|
||||
REQUIRE("1" == it->get("style"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to above test, but there's a detail brush sticking into the area portal
|
||||
*/
|
||||
TEST_CASE("areaportal_with_detail", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_areaportal_with_detail.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// area 0 is a placeholder
|
||||
// areaportal 0 is a placeholder
|
||||
//
|
||||
// the conceptual area portal has portalnum 1, and consists of two dareaportals entries with connections to area 1 and 2
|
||||
CHECK_THAT(bsp.dareaportals, Catch::Matchers::UnorderedEquals(std::vector<dareaportal_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
CHECK_THAT(bsp.dareas, Catch::Matchers::UnorderedEquals(std::vector<darea_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
}
|
||||
|
||||
TEST_CASE("nodraw_light", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_nodraw_light.map", {"-includeskip"});
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d topface_center {160, -148, 208};
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], topface_center, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/trigger");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_NODRAW));
|
||||
}
|
||||
|
||||
TEST_CASE("nodraw_detail_light", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_nodraw_detail_light.map", {"-includeskip"});
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d topface_center {160, -148, 208};
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], topface_center, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/trigger");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_NODRAW));
|
||||
}
|
||||
|
||||
TEST_CASE("base1", "[testmaps_q2][.releaseonly]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("base1-test.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CHECK(prt);
|
||||
CheckFilled(bsp);
|
||||
|
||||
// bspinfo output from a compile done with
|
||||
// https://github.com/qbism/q2tools-220 at 46fd97bbe1b3657ca9e93227f89aaf0fbd3677c9.
|
||||
// only took a couple of seconds (debug build)
|
||||
|
||||
// 35 models
|
||||
// 9918 planes 198360
|
||||
//10367 vertexes 124404
|
||||
// 5177 nodes 144956
|
||||
// 637 texinfos 48412
|
||||
// 7645 faces 152900
|
||||
// 5213 leafs 145964
|
||||
// 9273 leaffaces 18546
|
||||
// 7307 leafbrushes 14614
|
||||
//20143 edges 80572
|
||||
//37287 surfedges 149148
|
||||
// 1765 brushes 21180
|
||||
//15035 brushsides 60140
|
||||
// 3 areas 24
|
||||
// 3 areaportals 24
|
||||
// lightdata 0
|
||||
// visdata 0
|
||||
// entdata 53623
|
||||
|
||||
CHECK(3 == bsp.dareaportals.size());
|
||||
CHECK(3 == bsp.dareas.size());
|
||||
|
||||
// check for a sliver face which we had issues with being missing
|
||||
{
|
||||
const qvec3d face_point {-315.975, -208.036, -84.5};
|
||||
const qvec3d normal_point {-315.851, -208.051, -84.5072}; // obtained in TB
|
||||
|
||||
const qvec3d normal = qv::normalize(normal_point - face_point);
|
||||
|
||||
auto *sliver_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], face_point, normal);
|
||||
REQUIRE(nullptr != sliver_face);
|
||||
|
||||
CHECK(std::string_view("e1u1/metal3_5") == Face_TextureName(&bsp, sliver_face));
|
||||
CHECK(Face_Winding(&bsp, sliver_face).area() < 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("quake maps", "[testmaps_q1][.releaseonly]")
|
||||
{
|
||||
const std::vector<std::string> quake_maps{"DM1-test.map", "DM2-test.map", "DM3-test.map", "DM4-test.map",
|
||||
|
|
@ -1562,210 +1302,13 @@ TEST_CASE("chop", "[testmaps_q1][.releaseonly]")
|
|||
CheckFilled(bsp);
|
||||
}
|
||||
|
||||
TEST_CASE("base1leak", "[testmaps_q2]")
|
||||
TEST_CASE("mountain", "[testmaps_q1][.releaseonly][!mayfail]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("base1leak.map");
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_q1_mountain.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
CHECK(8 == bsp.dbrushes.size());
|
||||
|
||||
CHECK(bsp.dleafs.size() >= 8); // 1 placeholder + 1 empty (room interior) + 6 solid (sides of room)
|
||||
CHECK(bsp.dleafs.size() <= 12); //q2tools-220 generates 12
|
||||
|
||||
const qvec3d in_plus_y_wall{-776, 976, -24};
|
||||
auto *plus_y_wall_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_plus_y_wall);
|
||||
CHECK(Q2_CONTENTS_SOLID == plus_y_wall_leaf->contents);
|
||||
|
||||
CHECK(3 == plus_y_wall_leaf->numleafbrushes);
|
||||
|
||||
CHECK(prt->portals.size() == 0);
|
||||
CHECK(prt->portalleafs == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* e1u1/brlava brush intersecting e1u1/clip
|
||||
**/
|
||||
TEST_CASE("lavaclip", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_lavaclip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// not touching the lava, but inside the clip
|
||||
const qvec3d playerclip_outside1 { -88, -32, 8};
|
||||
const qvec3d playerclip_outside2 { 88, -32, 8};
|
||||
|
||||
// inside both clip and lava
|
||||
const qvec3d playerclip_inside_lava { 0, -32, 8};
|
||||
|
||||
const qvec3d in_lava_only {0, 32, 8};
|
||||
|
||||
// near the player start's feet. There should be a lava face here
|
||||
const qvec3d lava_top_face_in_playerclip { 0, -32, 16};
|
||||
|
||||
// check leaf contents
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_outside1)->contents);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_outside2)->contents);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_inside_lava)->contents);
|
||||
CHECK(Q2_CONTENTS_LAVA == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_lava_only)->contents);
|
||||
|
||||
// search for face
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], lava_top_face_in_playerclip, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/brlava");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_WARP));
|
||||
}
|
||||
|
||||
/**
|
||||
* check that e1u1/clip intersecting mist doesn't split up the mist faces
|
||||
**/
|
||||
TEST_CASE("mist_clip", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_mist_clip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// mist is two sided, so 12 faces for a cube
|
||||
CHECK(12 == bsp.dfaces.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* e1u1/brlava brush intersecting e1u1/brwater
|
||||
**/
|
||||
TEST_CASE("lavawater", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_lavawater.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d inside_both { 0, 32, 8};
|
||||
|
||||
// check leaf contents
|
||||
CHECK((Q2_CONTENTS_LAVA | Q2_CONTENTS_WATER) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_both)->contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Weird mystery issue with a func_wall with broken collision
|
||||
* (ended up being a PLANE_X/Y/Z plane with negative facing normal, which is illegal - engine assumes they are positive)
|
||||
*/
|
||||
TEST_CASE("qbsp_q2_bmodel_collision", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_bmodel_collision.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_bmodel {-544, -312, -258};
|
||||
REQUIRE(2 == bsp.dmodels.size());
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[1], in_bmodel)->contents);
|
||||
}
|
||||
|
||||
TEST_CASE("q2_liquids", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_liquids.map");
|
||||
|
||||
// water/air face is two sided
|
||||
{
|
||||
const qvec3d watertrans66_air{-116, -168, 144};
|
||||
const qvec3d watertrans33_trans66 = watertrans66_air - qvec3d(0, 0, 48);
|
||||
const qvec3d wateropaque_trans33 = watertrans33_trans66 - qvec3d(0, 0, 48);
|
||||
const qvec3d floor_wateropaque = wateropaque_trans33 - qvec3d(0, 0, 48);
|
||||
|
||||
CHECK_THAT(TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_air)),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/bluwter", "e1u1/bluwter"}));
|
||||
CHECK(0 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans33_trans66).size());
|
||||
CHECK(0 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], wateropaque_trans33).size());
|
||||
CHECK_THAT(TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], floor_wateropaque)),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/c_met11_2"}));
|
||||
}
|
||||
|
||||
const qvec3d watertrans66_slimetrans66{-116, -144, 116};
|
||||
|
||||
// water trans66 / slime trans66
|
||||
{
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_slimetrans66, qvec3d(0, -1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/sewer1"}));
|
||||
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_slimetrans66, qvec3d(0, 1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/sewer1"}));
|
||||
}
|
||||
|
||||
// slime trans66 / lava trans66
|
||||
const qvec3d slimetrans66_lavatrans66 = watertrans66_slimetrans66 + qvec3d(0, 48, 0);
|
||||
{
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], slimetrans66_lavatrans66, qvec3d(0, -1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/brlava"}));
|
||||
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], slimetrans66_lavatrans66, qvec3d(0, 1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/brlava"}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty rooms are sealed to solid in Q2
|
||||
**/
|
||||
TEST_CASE("qbsp_q2_seal_empty_rooms", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_seal_empty_rooms.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_start_room {-240, 80, 56};
|
||||
const qvec3d in_empty_room {-244, 476, 68};
|
||||
|
||||
// check leaf contents
|
||||
CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_empty_room)->contents);
|
||||
|
||||
CHECK(prt->portals.size() == 0);
|
||||
CHECK(prt->portalleafs == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail seals in Q2
|
||||
**/
|
||||
TEST_CASE("qbsp_q2_detail_seals", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_detail_seals.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_start_room {-240, 80, 56};
|
||||
const qvec3d in_void {-336, 80, 56};
|
||||
|
||||
// check leaf contents
|
||||
CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_void)->contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Two areaportals with a small gap in between creating another area.
|
||||
*
|
||||
* Also, the faces on the ceiling/floor cross the areaportal
|
||||
* (due to our aggressive face merging).
|
||||
*/
|
||||
TEST_CASE("q2_double_areaportal", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_double_areaportal.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CHECK(GAME_QUAKE == bsp.loadversion->game->id);
|
||||
CHECK(prt);
|
||||
CheckFilled(bsp);
|
||||
|
||||
CHECK(4 == bsp.dareas.size());
|
||||
CHECK(5 == bsp.dareaportals.size());
|
||||
}
|
||||
|
||||
TEST_CASE("q2_areaportal_split", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_areaportal_split.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CheckFilled(bsp);
|
||||
|
||||
CHECK(3 == bsp.dareas.size()); // 1 invalid index zero reserved + 2 areas
|
||||
CHECK(3 == bsp.dareaportals.size()); // 1 invalid index zero reserved + 2 dareaportals to store the two directions of the portal
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1809,24 +1352,6 @@ TEST_CASE("qbsp_q1_sealing", "[testmaps_q1]") {
|
|||
CHECK(prt->portalleafs_real == 3); // no detail, so same as above
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for q2 bmodel bounds
|
||||
**/
|
||||
TEST_CASE("q2_door", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_door.map");
|
||||
|
||||
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}};
|
||||
|
||||
CHECK(world_tight_bounds.mins() == bsp.dmodels[0].mins);
|
||||
CHECK(world_tight_bounds.maxs() == bsp.dmodels[0].maxs);
|
||||
|
||||
CHECK(bmodel_tight_bounds.mins() == bsp.dmodels[1].mins);
|
||||
CHECK(bmodel_tight_bounds.maxs() == bsp.dmodels[1].maxs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for WAD internal textures
|
||||
**/
|
||||
|
|
@ -1983,7 +1508,7 @@ static void CountClipnodeLeafsByContentType_r(const mbsp_t& bsp, int clipnode, s
|
|||
CountClipnodeLeafsByContentType_r(bsp, node.children[1], result);
|
||||
}
|
||||
|
||||
static std::map<int, int> CountClipnodeLeafsByContentType(const mbsp_t& bsp, int hullnum)
|
||||
std::map<int, int> CountClipnodeLeafsByContentType(const mbsp_t& bsp, int hullnum)
|
||||
{
|
||||
Q_assert(hullnum > 0);
|
||||
|
||||
|
|
@ -2009,7 +1534,7 @@ static int CountClipnodeNodes_r(const mbsp_t& bsp, int clipnode)
|
|||
/**
|
||||
* Count the non-leaf clipnodes of the worldmodel for the given hull's decision tree.
|
||||
*/
|
||||
static int CountClipnodeNodes(const mbsp_t& bsp, int hullnum)
|
||||
int CountClipnodeNodes(const mbsp_t& bsp, int hullnum)
|
||||
{
|
||||
Q_assert(hullnum > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#include <common/bspfile.hh>
|
||||
#include <common/prtfile.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class mapbrush_t;
|
||||
struct mapface_t;
|
||||
class mapface_t;
|
||||
class mapentity_t;
|
||||
|
||||
const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t &brush, const std::string &texname);
|
||||
mapentity_t &LoadMap(const char *map);
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args = {});
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ2(const std::filesystem::path &name, std::vector<std::string> extra_args = {});
|
||||
std::tuple<mbsp_t, bspxentries_t, std::optional<prtfile_t>> LoadTestmapQ1(const std::filesystem::path &name, std::vector<std::string> extra_args = {});
|
||||
void CheckFilled(const mbsp_t &bsp, hull_index_t hullnum);
|
||||
void CheckFilled(const mbsp_t &bsp);
|
||||
std::map<std::string, std::vector<const mface_t *>> MakeTextureToFaceMap(const mbsp_t &bsp);
|
||||
const texvecf &GetTexvecs(const char *map, const char *texname);
|
||||
std::vector<std::string> TexNames(const mbsp_t &bsp, std::vector<const mface_t *> faces);
|
||||
std::vector<const mface_t *> FacesWithTextureName(const mbsp_t &bsp, const std::string &name);
|
||||
std::map<int, int> CountClipnodeLeafsByContentType(const mbsp_t& bsp, int hullnum);
|
||||
int CountClipnodeNodes(const mbsp_t& bsp, int hullnum);
|
||||
bool PortalMatcher(const prtfile_winding_t& a, const prtfile_winding_t &b);
|
||||
std::map<int, int> CountClipnodeLeafsByContentType(const mbsp_t& bsp, int hullnum);
|
||||
int CountClipnodeNodes(const mbsp_t& bsp, int hullnum);
|
||||
|
|
@ -0,0 +1,516 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_vector.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <qbsp/brush.hh>
|
||||
#include <qbsp/brushbsp.hh>
|
||||
#include <qbsp/qbsp.hh>
|
||||
#include <qbsp/map.hh>
|
||||
#include <common/fs.hh>
|
||||
#include <common/bsputils.hh>
|
||||
#include <common/decompile.hh>
|
||||
#include <common/prtfile.hh>
|
||||
#include <common/qvec.hh>
|
||||
#include <testmaps.hh>
|
||||
|
||||
#include <subprocess.h>
|
||||
#include <nanobench.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <map>
|
||||
|
||||
#include <common/bspinfo.hh>
|
||||
|
||||
#include "test_qbsp.hh"
|
||||
|
||||
TEST_CASE("detail", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_detail.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// stats
|
||||
CHECK(1 == bsp.dmodels.size());
|
||||
// Q2 reserves leaf 0 as an invalid leaf
|
||||
|
||||
// leafs:
|
||||
// 6 solid leafs outside the room (* can be more depending on when the "divider" is cut)
|
||||
// 1 empty leaf filling the room above the divider
|
||||
// 2 empty leafs + 1 solid leaf for divider
|
||||
// 1 detail leaf for button
|
||||
// 4 empty leafs around + 1 on top of button
|
||||
|
||||
std::map<int32_t, int> counts_by_contents;
|
||||
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
|
||||
++counts_by_contents[bsp.dleafs[i].contents];
|
||||
}
|
||||
CHECK(2 == counts_by_contents.size()); // number of types
|
||||
|
||||
|
||||
CHECK(counts_by_contents.find(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == counts_by_contents.end()); // the detail bit gets cleared
|
||||
CHECK(8 == counts_by_contents.at(0)); // empty leafs
|
||||
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) >= 8);
|
||||
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) <= 12);
|
||||
|
||||
// clusters:
|
||||
// 1 empty cluster filling the room above the divider
|
||||
// 2 empty clusters created by divider
|
||||
// 1 cluster for the part of the room with the button
|
||||
|
||||
std::set<int> clusters;
|
||||
// first add the empty leafs
|
||||
for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
|
||||
if (0 == bsp.dleafs[i].contents) {
|
||||
clusters.insert(bsp.dleafs[i].cluster);
|
||||
}
|
||||
}
|
||||
CHECK(4 == clusters.size());
|
||||
|
||||
// various points in the main room cluster
|
||||
const qvec3d under_button{246, 436, 96}; // directly on the main floor plane
|
||||
const qvec3d inside_button{246, 436, 98};
|
||||
const qvec3d above_button{246, 436, 120};
|
||||
const qvec3d beside_button{246, 400, 100}; // should be a different empty leaf than above_button, but same cluster
|
||||
|
||||
// side room (different cluster)
|
||||
const qvec3d side_room{138, 576, 140};
|
||||
|
||||
// detail clips away world faces
|
||||
CHECK(nullptr == BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], under_button, {0, 0, 1}));
|
||||
|
||||
// check for correct contents
|
||||
auto *detail_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_button);
|
||||
CHECK(Q2_CONTENTS_SOLID == detail_leaf->contents);
|
||||
CHECK(-1 == detail_leaf->cluster);
|
||||
|
||||
// check for button (detail) brush
|
||||
CHECK(1 == Leaf_Brushes(&bsp, detail_leaf).size());
|
||||
CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) ==
|
||||
Leaf_Brushes(&bsp, detail_leaf).at(0)->contents);
|
||||
|
||||
// get more leafs
|
||||
auto *empty_leaf_above_button = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], above_button);
|
||||
CHECK(0 == empty_leaf_above_button->contents);
|
||||
CHECK(0 == Leaf_Brushes(&bsp, empty_leaf_above_button).size());
|
||||
|
||||
auto *empty_leaf_side_room = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], side_room);
|
||||
CHECK(0 == empty_leaf_side_room->contents);
|
||||
CHECK(0 == Leaf_Brushes(&bsp, empty_leaf_side_room).size());
|
||||
CHECK(empty_leaf_side_room->cluster != empty_leaf_above_button->cluster);
|
||||
|
||||
auto *empty_leaf_beside_button = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], beside_button);
|
||||
CHECK(0 == empty_leaf_beside_button->contents);
|
||||
CHECK(-1 != empty_leaf_beside_button->cluster);
|
||||
CHECK(empty_leaf_above_button->cluster == empty_leaf_beside_button->cluster);
|
||||
CHECK(empty_leaf_above_button != empty_leaf_beside_button);
|
||||
|
||||
CHECK(prt->portals.size() == 5);
|
||||
CHECK(prt->portalleafs_real == 0); // not used by Q2
|
||||
CHECK(prt->portalleafs == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("playerclip", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_playerclip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_playerclip{32, -136, 144};
|
||||
auto *playerclip_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_playerclip);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_DETAIL) == playerclip_leaf->contents);
|
||||
|
||||
// make sure faces at these locations aren't clipped away
|
||||
const qvec3d floor_under_clip{32, -136, 96};
|
||||
const qvec3d pillar_side_in_clip1{32, -48, 144};
|
||||
const qvec3d pillar_side_in_clip2{32, -208, 144};
|
||||
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_under_clip, {0, 0, 1}));
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], pillar_side_in_clip1, {0, -1, 0}));
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], pillar_side_in_clip2, {0, 1, 0}));
|
||||
|
||||
// make sure no face is generated for the playerclip brush
|
||||
const qvec3d playerclip_front_face{16, -152, 144};
|
||||
CHECK(nullptr == BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], playerclip_front_face, {-1, 0, 0}));
|
||||
|
||||
// check for brush
|
||||
CHECK(1 == Leaf_Brushes(&bsp, playerclip_leaf).size());
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_DETAIL) == Leaf_Brushes(&bsp, playerclip_leaf).at(0)->contents);
|
||||
}
|
||||
|
||||
TEST_CASE("areaportal", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_areaportal.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// area 0 is a placeholder
|
||||
// areaportal 0 is a placeholder
|
||||
//
|
||||
// the conceptual area portal has portalnum 1, and consists of two dareaportals entries with connections to area 1 and 2
|
||||
CHECK_THAT(bsp.dareaportals, Catch::Matchers::UnorderedEquals(std::vector<dareaportal_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
CHECK_THAT(bsp.dareas, Catch::Matchers::UnorderedEquals(std::vector<darea_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
|
||||
// look up the leafs
|
||||
const qvec3d player_start{-88, -112, 120};
|
||||
const qvec3d other_room{128, -112, 120};
|
||||
const qvec3d areaportal_pos{32, -112, 120};
|
||||
const qvec3d void_pos{-408, -112, 120};
|
||||
|
||||
auto *player_start_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_start);
|
||||
auto *other_room_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], other_room);
|
||||
auto *areaportal_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], areaportal_pos);
|
||||
auto *void_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], void_pos);
|
||||
|
||||
// check leaf contents
|
||||
CHECK(0 == player_start_leaf->contents);
|
||||
CHECK(0 == other_room_leaf->contents);
|
||||
CHECK(Q2_CONTENTS_AREAPORTAL == areaportal_leaf->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == void_leaf->contents);
|
||||
|
||||
// make sure faces at these locations aren't clipped away
|
||||
const qvec3d floor_under_areaportal{32, -136, 96};
|
||||
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_under_areaportal, {0, 0, 1}));
|
||||
|
||||
// check for brushes
|
||||
CHECK(1 == Leaf_Brushes(&bsp, areaportal_leaf).size());
|
||||
CHECK(Q2_CONTENTS_AREAPORTAL == Leaf_Brushes(&bsp, areaportal_leaf).at(0)->contents);
|
||||
|
||||
CHECK(1 == Leaf_Brushes(&bsp, void_leaf).size());
|
||||
CHECK(Q2_CONTENTS_SOLID == Leaf_Brushes(&bsp, void_leaf).at(0)->contents);
|
||||
|
||||
// check leaf areas
|
||||
CHECK_THAT((std::vector<int32_t>{1, 2}), Catch::Matchers::UnorderedEquals(std::vector<int32_t>{player_start_leaf->area, other_room_leaf->area}));
|
||||
// the areaportal leaf itself actually gets assigned to one of the two sides' areas
|
||||
CHECK((areaportal_leaf->area == 1 || areaportal_leaf->area == 2));
|
||||
CHECK(0 == void_leaf->area); // a solid leaf gets the invalid area
|
||||
|
||||
// check the func_areaportal entity had its "style" set
|
||||
parser_t parser(bsp.dentdata, { "qbsp_q2_areaportal.bsp" });
|
||||
auto ents = EntData_Parse(parser);
|
||||
auto it = std::find_if(ents.begin(), ents.end(),
|
||||
[](const entdict_t &dict) { return dict.get("classname") == "func_areaportal"; });
|
||||
|
||||
REQUIRE(it != ents.end());
|
||||
REQUIRE("1" == it->get("style"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to above test, but there's a detail brush sticking into the area portal
|
||||
*/
|
||||
TEST_CASE("areaportal_with_detail", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_areaportal_with_detail.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// area 0 is a placeholder
|
||||
// areaportal 0 is a placeholder
|
||||
//
|
||||
// the conceptual area portal has portalnum 1, and consists of two dareaportals entries with connections to area 1 and 2
|
||||
CHECK_THAT(bsp.dareaportals, Catch::Matchers::UnorderedEquals(std::vector<dareaportal_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
CHECK_THAT(bsp.dareas, Catch::Matchers::UnorderedEquals(std::vector<darea_t>{{0, 0}, {1, 1}, {1, 2}}));
|
||||
}
|
||||
|
||||
TEST_CASE("nodraw_light", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_nodraw_light.map", {"-includeskip"});
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d topface_center {160, -148, 208};
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], topface_center, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/trigger");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_NODRAW));
|
||||
}
|
||||
|
||||
TEST_CASE("nodraw_detail_light", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_nodraw_detail_light.map", {"-includeskip"});
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d topface_center {160, -148, 208};
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], topface_center, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/trigger");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_NODRAW));
|
||||
}
|
||||
|
||||
TEST_CASE("base1", "[testmaps_q2][.releaseonly]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("base1-test.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CHECK(prt);
|
||||
CheckFilled(bsp);
|
||||
|
||||
// bspinfo output from a compile done with
|
||||
// https://github.com/qbism/q2tools-220 at 46fd97bbe1b3657ca9e93227f89aaf0fbd3677c9.
|
||||
// only took a couple of seconds (debug build)
|
||||
|
||||
// 35 models
|
||||
// 9918 planes 198360
|
||||
//10367 vertexes 124404
|
||||
// 5177 nodes 144956
|
||||
// 637 texinfos 48412
|
||||
// 7645 faces 152900
|
||||
// 5213 leafs 145964
|
||||
// 9273 leaffaces 18546
|
||||
// 7307 leafbrushes 14614
|
||||
//20143 edges 80572
|
||||
//37287 surfedges 149148
|
||||
// 1765 brushes 21180
|
||||
//15035 brushsides 60140
|
||||
// 3 areas 24
|
||||
// 3 areaportals 24
|
||||
// lightdata 0
|
||||
// visdata 0
|
||||
// entdata 53623
|
||||
|
||||
CHECK(3 == bsp.dareaportals.size());
|
||||
CHECK(3 == bsp.dareas.size());
|
||||
|
||||
// check for a sliver face which we had issues with being missing
|
||||
{
|
||||
const qvec3d face_point {-315.975, -208.036, -84.5};
|
||||
const qvec3d normal_point {-315.851, -208.051, -84.5072}; // obtained in TB
|
||||
|
||||
const qvec3d normal = qv::normalize(normal_point - face_point);
|
||||
|
||||
auto *sliver_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], face_point, normal);
|
||||
REQUIRE(nullptr != sliver_face);
|
||||
|
||||
CHECK(std::string_view("e1u1/metal3_5") == Face_TextureName(&bsp, sliver_face));
|
||||
CHECK(Face_Winding(&bsp, sliver_face).area() < 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("base1leak", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("base1leak.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
CHECK(8 == bsp.dbrushes.size());
|
||||
|
||||
CHECK(bsp.dleafs.size() >= 8); // 1 placeholder + 1 empty (room interior) + 6 solid (sides of room)
|
||||
CHECK(bsp.dleafs.size() <= 12); //q2tools-220 generates 12
|
||||
|
||||
const qvec3d in_plus_y_wall{-776, 976, -24};
|
||||
auto *plus_y_wall_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_plus_y_wall);
|
||||
CHECK(Q2_CONTENTS_SOLID == plus_y_wall_leaf->contents);
|
||||
|
||||
CHECK(3 == plus_y_wall_leaf->numleafbrushes);
|
||||
|
||||
CHECK(prt->portals.size() == 0);
|
||||
CHECK(prt->portalleafs == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* e1u1/brlava brush intersecting e1u1/clip
|
||||
**/
|
||||
TEST_CASE("lavaclip", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_lavaclip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// not touching the lava, but inside the clip
|
||||
const qvec3d playerclip_outside1 { -88, -32, 8};
|
||||
const qvec3d playerclip_outside2 { 88, -32, 8};
|
||||
|
||||
// inside both clip and lava
|
||||
const qvec3d playerclip_inside_lava { 0, -32, 8};
|
||||
|
||||
const qvec3d in_lava_only {0, 32, 8};
|
||||
|
||||
// near the player start's feet. There should be a lava face here
|
||||
const qvec3d lava_top_face_in_playerclip { 0, -32, 16};
|
||||
|
||||
// check leaf contents
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_outside1)->contents);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_outside2)->contents);
|
||||
CHECK((Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], playerclip_inside_lava)->contents);
|
||||
CHECK(Q2_CONTENTS_LAVA == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_lava_only)->contents);
|
||||
|
||||
// search for face
|
||||
auto *topface = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], lava_top_face_in_playerclip, {0, 0, 1});
|
||||
REQUIRE(nullptr != topface);
|
||||
|
||||
auto *texinfo = Face_Texinfo(&bsp, topface);
|
||||
CHECK(std::string(texinfo->texture.data()) == "e1u1/brlava");
|
||||
CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_WARP));
|
||||
}
|
||||
|
||||
/**
|
||||
* check that e1u1/clip intersecting mist doesn't split up the mist faces
|
||||
**/
|
||||
TEST_CASE("mist_clip", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_mist_clip.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
// mist is two sided, so 12 faces for a cube
|
||||
CHECK(12 == bsp.dfaces.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* e1u1/brlava brush intersecting e1u1/brwater
|
||||
**/
|
||||
TEST_CASE("lavawater", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_lavawater.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d inside_both { 0, 32, 8};
|
||||
|
||||
// check leaf contents
|
||||
CHECK((Q2_CONTENTS_LAVA | Q2_CONTENTS_WATER) == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_both)->contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Weird mystery issue with a func_wall with broken collision
|
||||
* (ended up being a PLANE_X/Y/Z plane with negative facing normal, which is illegal - engine assumes they are positive)
|
||||
*/
|
||||
TEST_CASE("qbsp_q2_bmodel_collision", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_bmodel_collision.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_bmodel {-544, -312, -258};
|
||||
REQUIRE(2 == bsp.dmodels.size());
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[1], in_bmodel)->contents);
|
||||
}
|
||||
|
||||
TEST_CASE("q2_liquids", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_liquids.map");
|
||||
|
||||
// water/air face is two sided
|
||||
{
|
||||
const qvec3d watertrans66_air{-116, -168, 144};
|
||||
const qvec3d watertrans33_trans66 = watertrans66_air - qvec3d(0, 0, 48);
|
||||
const qvec3d wateropaque_trans33 = watertrans33_trans66 - qvec3d(0, 0, 48);
|
||||
const qvec3d floor_wateropaque = wateropaque_trans33 - qvec3d(0, 0, 48);
|
||||
|
||||
CHECK_THAT(TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_air)),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/bluwter", "e1u1/bluwter"}));
|
||||
CHECK(0 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans33_trans66).size());
|
||||
CHECK(0 == BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], wateropaque_trans33).size());
|
||||
CHECK_THAT(TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], floor_wateropaque)),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/c_met11_2"}));
|
||||
}
|
||||
|
||||
const qvec3d watertrans66_slimetrans66{-116, -144, 116};
|
||||
|
||||
// water trans66 / slime trans66
|
||||
{
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_slimetrans66, qvec3d(0, -1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/sewer1"}));
|
||||
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], watertrans66_slimetrans66, qvec3d(0, 1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/sewer1"}));
|
||||
}
|
||||
|
||||
// slime trans66 / lava trans66
|
||||
const qvec3d slimetrans66_lavatrans66 = watertrans66_slimetrans66 + qvec3d(0, 48, 0);
|
||||
{
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], slimetrans66_lavatrans66, qvec3d(0, -1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/brlava"}));
|
||||
|
||||
CHECK_THAT(
|
||||
TexNames(bsp, BSP_FindFacesAtPoint(&bsp, &bsp.dmodels[0], slimetrans66_lavatrans66, qvec3d(0, 1, 0))),
|
||||
Catch::Matchers::UnorderedEquals<std::string>({"e1u1/brlava"}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty rooms are sealed to solid in Q2
|
||||
**/
|
||||
TEST_CASE("qbsp_q2_seal_empty_rooms", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_seal_empty_rooms.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_start_room {-240, 80, 56};
|
||||
const qvec3d in_empty_room {-244, 476, 68};
|
||||
|
||||
// check leaf contents
|
||||
CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_empty_room)->contents);
|
||||
|
||||
CHECK(prt->portals.size() == 0);
|
||||
CHECK(prt->portalleafs == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail seals in Q2
|
||||
**/
|
||||
TEST_CASE("qbsp_q2_detail_seals", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("qbsp_q2_detail_seals.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
|
||||
const qvec3d in_start_room {-240, 80, 56};
|
||||
const qvec3d in_void {-336, 80, 56};
|
||||
|
||||
// check leaf contents
|
||||
CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents);
|
||||
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_void)->contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Two areaportals with a small gap in between creating another area.
|
||||
*
|
||||
* Also, the faces on the ceiling/floor cross the areaportal
|
||||
* (due to our aggressive face merging).
|
||||
*/
|
||||
TEST_CASE("q2_double_areaportal", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_double_areaportal.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CheckFilled(bsp);
|
||||
|
||||
CHECK(4 == bsp.dareas.size());
|
||||
CHECK(5 == bsp.dareaportals.size());
|
||||
}
|
||||
|
||||
TEST_CASE("q2_areaportal_split", "[testmaps_q2]")
|
||||
{
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_areaportal_split.map");
|
||||
|
||||
CHECK(GAME_QUAKE_II == bsp.loadversion->game->id);
|
||||
CheckFilled(bsp);
|
||||
|
||||
CHECK(3 == bsp.dareas.size()); // 1 invalid index zero reserved + 2 areas
|
||||
CHECK(3 == bsp.dareaportals.size()); // 1 invalid index zero reserved + 2 dareaportals to store the two directions of the portal
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for q2 bmodel bounds
|
||||
**/
|
||||
TEST_CASE("q2_door", "[testmaps_q2]") {
|
||||
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_door.map");
|
||||
|
||||
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}};
|
||||
|
||||
CHECK(world_tight_bounds.mins() == bsp.dmodels[0].mins);
|
||||
CHECK(world_tight_bounds.maxs() == bsp.dmodels[0].maxs);
|
||||
|
||||
CHECK(bmodel_tight_bounds.mins() == bsp.dmodels[1].mins);
|
||||
CHECK(bmodel_tight_bounds.maxs() == bsp.dmodels[1].maxs);
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
|
||||
Loading…
Reference in New Issue