diff --git a/include/common/qvec.hh b/include/common/qvec.hh index 15ec8d35..a5d38b6d 100644 --- a/include/common/qvec.hh +++ b/include/common/qvec.hh @@ -106,6 +106,10 @@ public: return true; } + bool operator!=(const qvec &other) const { + return !(*this == other); + } + T operator[](const int idx) const { assert(idx >= 0 && idx < N); return v[idx]; diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index a82a9025..af727f8e 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -195,4 +195,7 @@ void ExportObj_Surfaces(const surface_t *surfaces); void WriteBspBrushMap(const char *name, const std::vector &list); +bool IsValidTextureProjection(const qvec3f &faceNormal, const qvec3f &s_vec, const qvec3f &t_vec); + + #endif diff --git a/qbsp/map.cc b/qbsp/map.cc index 56b6c517..ac035f3f 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -1394,6 +1394,26 @@ void mapface_t::set_texvecs(const std::array &vecs) this->texinfo = FindTexinfo( &texInfoNew, texInfoNew.flags ); } +bool +IsValidTextureProjection(const qvec3f &faceNormal, const qvec3f &s_vec, const qvec3f &t_vec) +{ + // TODO: This doesn't match how light does it (TexSpaceToWorld) + + const qvec3f tex_normal = qv::normalize(qv::cross(s_vec, t_vec)); + + for (int i=0;i<3;i++) + if (std::isnan(tex_normal[i])) + return false; + + const float cosangle = qv::dot(tex_normal, faceNormal); + if (std::isnan(cosangle)) + return false; + if (fabs(cosangle) < ZERO_EPSILON) + return false; + + return true; +} + static std::unique_ptr ParseBrushFace(parser_t *parser, const mapbrush_t *brush, const mapentity_t *entity) { diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index df380367..6bec21cb 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -307,6 +307,94 @@ TEST(qbsp, MemLeaks) { } #endif +/** + * Test that this skip face gets auto-corrected. + */ +TEST(qbsp, InvalidTextureProjection) { + const char *map = R"( + // entity 0 + { + "classname" "worldspawn" + // brush 0 + { + ( -64 -64 -16 ) ( -64 -63 -16 ) ( -64 -64 -15 ) +2butn [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) +2butn [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) +2butn [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) +2butn [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 64 ) ( 64 65 64 ) ( 65 64 64 ) +2butn [ 1 0 0 -0 ] [ 0 -1 0 -0 ] -0 1 1 + ( -64 -64 -0 ) ( -63 -64 -0 ) ( -64 -63 -0 ) skip [ 0 0 0 0 ] [ 0 0 0 0 ] -0 1 1 + } + } + )"; + + mapentity_t worldspawn = LoadMap(map); + Q_assert(1 == worldspawn.nummapbrushes); + + const mapface_t *face = &worldspawn.mapbrush(0).face(5); + ASSERT_EQ("skip", face->texname); + const auto texvecs = face->get_texvecs(); + EXPECT_TRUE(IsValidTextureProjection(vec3_t_to_glm(face->plane.normal), texvecs.at(0), texvecs.at(1))); +} + +/** + * Same as above but the texture scales are 0 + */ +TEST(qbsp, InvalidTextureProjection2) { + const char *map = R"( + // entity 0 + { + "classname" "worldspawn" + // brush 0 + { + ( -64 -64 -16 ) ( -64 -63 -16 ) ( -64 -64 -15 ) +2butn [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) +2butn [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) +2butn [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) +2butn [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 + ( 64 64 64 ) ( 64 65 64 ) ( 65 64 64 ) +2butn [ 1 0 0 -0 ] [ 0 -1 0 -0 ] -0 1 1 + ( -64 -64 -0 ) ( -63 -64 -0 ) ( -64 -63 -0 ) skip [ 0 0 0 0 ] [ 0 0 0 0 ] -0 0 0 + } + } + )"; + + mapentity_t worldspawn = LoadMap(map); + Q_assert(1 == worldspawn.nummapbrushes); + + const mapface_t *face = &worldspawn.mapbrush(0).face(5); + ASSERT_EQ("skip", face->texname); + const auto texvecs = face->get_texvecs(); + EXPECT_TRUE(IsValidTextureProjection(vec3_t_to_glm(face->plane.normal), texvecs.at(0), texvecs.at(1))); +} + +/** + * More realistic: *lava1 has tex vecs perpendicular to face + */ +TEST(qbsp, InvalidTextureProjection3) { + const char *map = R"( + // entity 0 + { + "classname" "worldspawn" + "wad" "Q.wad" + // brush 0 + { + ( 512 512 64 ) ( 512 512 -0 ) ( 512 448 64 ) *04mwat1 [ 0 1 0 0 ] [ 0 0 -1 0 ] -0 1 1 + ( -0 448 -0 ) ( -0 512 -0 ) ( -0 448 64 ) *04mwat1 [ 0 -1 0 0 ] [ -0 -0 -1 0 ] -0 1 1 + ( 512 512 64 ) ( -0 512 64 ) ( 512 512 -0 ) *04mwat1 [ -1 0 0 0 ] [ 0 0 -1 0 ] -0 1 1 + ( -0 448 -0 ) ( -0 448 64 ) ( 512 448 -0 ) *lava1 [ 0 1 0 0 ] [ 0 0 -1 0 ] -0 1 1 + ( 512 512 64 ) ( 512 448 64 ) ( -0 512 64 ) *04mwat1 [ 1 0 0 0 ] [ 0 -1 0 0 ] -0 1 1 + ( -0 448 -0 ) ( 512 448 -0 ) ( -0 512 -0 ) *04mwat1 [ -1 0 0 0 ] [ -0 -1 -0 -0 ] -0 1 1 + } + } + )"; + + mapentity_t worldspawn = LoadMap(map); + Q_assert(1 == worldspawn.nummapbrushes); + + const mapface_t *face = &worldspawn.mapbrush(0).face(3); + ASSERT_EQ("*lava1", face->texname); + const auto texvecs = face->get_texvecs(); + EXPECT_TRUE(IsValidTextureProjection(vec3_t_to_glm(face->plane.normal), texvecs.at(0), texvecs.at(1))); +} + TEST(mathlib, WindingArea) { winding_t w; w.numpoints = 5; diff --git a/qbsp/util.cc b/qbsp/util.cc index 0850dc14..108b7dd2 100644 --- a/qbsp/util.cc +++ b/qbsp/util.cc @@ -71,6 +71,7 @@ AllocMem(int Type, int cElements, bool fZero) if (Type == FACE && cElements == 1) ((face_t *)pTemp)->planenum = -1; if (Type == WINDING) { + // FIXME: Remove this! Causing UBSan warnings *(int *)pTemp = cSize; pTemp = (char *)pTemp + sizeof(int); }