/* Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2017 Eric Wasylishen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; /* return 0 if either vector is zero-length */ static float AngleBetweenVectors(const qvec3f &d1, const qvec3f &d2) { float length_product = (qv::length(d1)*qv::length(d2)); if (length_product == 0) return 0; float cosangle = qv::dot(d1, d2)/length_product; if (cosangle < -1) cosangle = -1; if (cosangle > 1) cosangle = 1; float angle = acos(cosangle); return angle; } /* returns the angle between vectors p2->p1 and p2->p3 */ static float AngleBetweenPoints(const qvec3f &p1, const qvec3f &p2, const qvec3f &p3) { const qvec3f d1 = p1 - p2; const qvec3f d2 = p3 - p2; float result = AngleBetweenVectors(d1, d2); return result; } static bool s_builtPhongCaches; static std::map> vertex_normals; static std::set interior_verts; static map> smoothFaces; static map> vertsToFaces; static map> planesToFaces; static edgeToFaceMap_t EdgeToFaceMap; static vector FaceCache; const edgeToFaceMap_t &GetEdgeToFaceMap() { Q_assert(s_builtPhongCaches); return EdgeToFaceMap; } // Uses `smoothFaces` static var bool FacesSmoothed(const bsp2_dface_t *f1, const bsp2_dface_t *f2) { Q_assert(s_builtPhongCaches); const auto &facesIt = smoothFaces.find(f1); if (facesIt == smoothFaces.end()) return false; const set &faceSet = facesIt->second; if (faceSet.find(f2) == faceSet.end()) return false; return true; } const std::set &GetSmoothFaces(const bsp2_dface_t *face) { Q_assert(s_builtPhongCaches); static std::set empty; const auto it = smoothFaces.find(face); if (it == smoothFaces.end()) return empty; return it->second; } const std::vector &GetPlaneFaces(const bsp2_dface_t *face) { Q_assert(s_builtPhongCaches); static std::vector empty; const auto it = planesToFaces.find(face->planenum); if (it == planesToFaces.end()) return empty; return it->second; } /* given a triangle, just adds the contribution from the triangle to the given vertexes normals, based upon angles at the verts. * v1, v2, v3 are global vertex indices */ static void AddTriangleNormals(std::map &smoothed_normals, const qvec3f &norm, const mbsp_t *bsp, int v1, int v2, int v3) { const qvec3f p1 = Vertex_GetPos_E(bsp, v1); const qvec3f p2 = Vertex_GetPos_E(bsp, v2); const qvec3f p3 = Vertex_GetPos_E(bsp, v3); float weight; weight = AngleBetweenPoints(p2, p1, p3); smoothed_normals[v1] = smoothed_normals[v1] + (norm * weight); weight = AngleBetweenPoints(p1, p2, p3); smoothed_normals[v2] = smoothed_normals[v2] + (norm * weight); weight = AngleBetweenPoints(p1, p3, p2); smoothed_normals[v3] = smoothed_normals[v3] + (norm * weight); } /* access the final phong-shaded vertex normal */ const qvec3f GetSurfaceVertexNormal(const mbsp_t *bsp, const bsp2_dface_t *f, const int vertindex) { Q_assert(s_builtPhongCaches); // handle degenerate faces const auto it = vertex_normals.find(f); if (it == vertex_normals.end()) { return qvec3f(0,0,0); } const auto &face_normals_vec = it->second; return face_normals_vec.at(vertindex); } static bool FacesOnSamePlane(const std::vector &faces) { if (faces.empty()) { return false; } const int32_t planenum = faces.at(0)->planenum; for (auto face : faces) { if (face->planenum != planenum) { return false; } } return true; } const bsp2_dface_t * Face_EdgeIndexSmoothed(const mbsp_t *bsp, const bsp2_dface_t *f, const int edgeindex) { Q_assert(s_builtPhongCaches); const int v0 = Face_VertexAtIndex(bsp, f, edgeindex); const int v1 = Face_VertexAtIndex(bsp, f, (edgeindex + 1) % f->numedges); auto it = EdgeToFaceMap.find(make_pair(v1, v0)); if (it != EdgeToFaceMap.end()) { for (const bsp2_dface_t *neighbour : it->second) { if (neighbour == f) { // Invalid face, e.g. with vertex numbers: [0, 1, 0, 2] continue; } // Check if these faces are smoothed or on the same plane if (!(FacesSmoothed(f, neighbour) || neighbour->planenum == f->planenum)) { continue; } return neighbour; } } return nullptr; #if 0 if (smoothFaces.find(f) == smoothFaces.end()) { return nullptr; } int v0 = Face_VertexAtIndex(bsp, f, edgeindex); int v1 = Face_VertexAtIndex(bsp, f, (edgeindex + 1) % f->numedges); const auto &v0_faces = vertsToFaces.at(v0); const auto &v1_faces = vertsToFaces.at(v1); // find a face f2 that has both verts v0 and v1 for (auto f2 : v0_faces) { if (f2 == f) continue; if (find(v1_faces.begin(), v1_faces.end(), f2) != v1_faces.end()) { const auto &f_smoothfaces = smoothFaces.at(f); bool smoothed = (f_smoothfaces.find(f2) != f_smoothfaces.end()); return smoothed ? f2 : nullptr; } } return nullptr; #endif } static edgeToFaceMap_t MakeEdgeToFaceMap(const mbsp_t *bsp) { edgeToFaceMap_t result; for (int i = 0; i < bsp->numfaces; i++) { const bsp2_dface_t *f = BSP_GetFace(bsp, i); // walk edges for (int j = 0; j < f->numedges; j++) { const int v0 = Face_VertexAtIndex(bsp, f, j); const int v1 = Face_VertexAtIndex(bsp, f, (j + 1) % f->numedges); if (v0 == v1) { // ad_swampy.bsp has faces with repeated verts... continue; } const auto edge = make_pair(v0, v1); auto &edgeFacesRef = result[edge]; Q_assert(find(begin(edgeFacesRef), end(edgeFacesRef), f) == end(edgeFacesRef)); edgeFacesRef.push_back(f); } } return result; } static vector Face_VertexNormals(const mbsp_t *bsp, const bsp2_dface_t *face) { vector normals; for (int i=0; inumedges; i++) { const qvec3f n = GetSurfaceVertexNormal(bsp, face, i); normals.push_back(n); } return normals; } static vector MakeFaceCache(const mbsp_t *bsp) { vector result; for (int i=0; inumfaces; i++) { const bsp2_dface_t *face = BSP_GetFace(bsp, i); result.push_back(face_cache_t{bsp, face, Face_VertexNormals(bsp, face)}); } return result; } void CalcualateVertexNormals(const mbsp_t *bsp) { Q_assert(!s_builtPhongCaches); s_builtPhongCaches = true; EdgeToFaceMap = MakeEdgeToFaceMap(bsp); // read _phong and _phong_angle from entities for compatiblity with other qbsp's, at the expense of no // support on func_detail/func_group for (int i=0; inummodels; i++) { const modelinfo_t *info = ModelInfoForModel(bsp, i); const uint8_t phongangle_byte = (uint8_t) qmax(0, qmin(255, (int)rint(info->getResolvedPhongAngle()))); if (!phongangle_byte) continue; for (int j=info->model->firstface; j < info->model->firstface + info->model->numfaces; j++) { const bsp2_dface_t *f = BSP_GetFace(bsp, j); extended_texinfo_flags[f->texinfo] &= ~(TEX_PHONG_ANGLE_MASK); extended_texinfo_flags[f->texinfo] |= (phongangle_byte << TEX_PHONG_ANGLE_SHIFT); } } // build "plane -> faces" map for (int i = 0; i < bsp->numfaces; i++) { const bsp2_dface_t *f = BSP_GetFace(bsp, i); planesToFaces[f->planenum].push_back(f); } // build "vert index -> faces" map for (int i = 0; i < bsp->numfaces; i++) { const bsp2_dface_t *f = BSP_GetFace(bsp, i); for (int j = 0; j < f->numedges; j++) { const int v = Face_VertexAtIndex(bsp, f, j); vertsToFaces[v].push_back(f); } } // track "interior" verts, these are in the middle of a face, and mess up normal interpolation for (int i=0; inumvertexes; i++) { auto &faces = vertsToFaces[i]; if (faces.size() > 1 && FacesOnSamePlane(faces)) { interior_verts.insert(i); } } //printf("CalcualateVertexNormals: %d interior verts\n", (int)interior_verts.size()); // build the "face -> faces to smooth with" map for (int i = 0; i < bsp->numfaces; i++) { const bsp2_dface_t *f = BSP_GetFace(const_cast(bsp), i); const qvec3f f_norm = Face_Normal_E(bsp, f); // any face normal within this many degrees can be smoothed with this face const int f_smoothangle = (extended_texinfo_flags[f->texinfo] & TEX_PHONG_ANGLE_MASK) >> TEX_PHONG_ANGLE_SHIFT; if (!f_smoothangle) continue; for (int j = 0; j < f->numedges; j++) { const int v = Face_VertexAtIndex(bsp, f, j); // walk over all faces incident to f (we will walk over neighbours multiple times, doesn't matter) for (const bsp2_dface_t *f2 : vertsToFaces[v]) { if (f2 == f) continue; const int f2_smoothangle = (extended_texinfo_flags[f2->texinfo] & TEX_PHONG_ANGLE_MASK) >> TEX_PHONG_ANGLE_SHIFT; if (!f2_smoothangle) continue; const qvec3f f2_norm = Face_Normal_E(bsp, f2); const vec_t cosangle = qv::dot(f_norm, f2_norm); const vec_t cosmaxangle = cos(DEG2RAD(qmin(f_smoothangle, f2_smoothangle))); // check the angle between the face normals if (cosangle >= cosmaxangle) { smoothFaces[f].insert(f2); } } } } // finally do the smoothing for each face for (int i = 0; i < bsp->numfaces; i++) { const bsp2_dface_t *f = BSP_GetFace(bsp, i); if (f->numedges < 3) { logprint("CalcualateVertexNormals: face %d is degenerate with %d edges\n", i, f->numedges); for (int j = 0; jnumedges; j++) { vec3_t pt; Face_PointAtIndex(bsp, f, j, pt); logprint(" vert at %f %f %f\n", pt[0], pt[1], pt[2]); } continue; } const auto &neighboursToSmooth = smoothFaces[f]; const qvec3f f_norm = Face_Normal_E(bsp, f); // get the face normal // gather up f and neighboursToSmooth std::vector fPlusNeighbours; fPlusNeighbours.push_back(f); for (auto neighbour : neighboursToSmooth) { fPlusNeighbours.push_back(neighbour); } // global vertex index -> smoothed normal std::map smoothedNormals; // walk fPlusNeighbours for (auto f2 : fPlusNeighbours) { const qvec3f f2_norm = Face_Normal_E(bsp, f2); /* now just walk around the surface as a triangle fan */ int v1, v2, v3; v1 = Face_VertexAtIndex(bsp, f2, 0); v2 = Face_VertexAtIndex(bsp, f2, 1); for (int j = 2; j < f2->numedges; j++) { v3 = Face_VertexAtIndex(bsp, f2, j); AddTriangleNormals(smoothedNormals, f2_norm, bsp, v1, v2, v3); v2 = v3; } } // normalize vertex normals (NOTE: updates smoothedNormals map) for (auto &pair : smoothedNormals) { const int vertIndex = pair.first; const qvec3f vertNormal = pair.second; if (0 == qv::length(vertNormal)) { // this happens when there are colinear vertices, which give zero-area triangles, // so there is no contribution to the normal of the triangle in the middle of the // line. Not really an error, just set it to use the face normal. #if 0 logprint("Failed to calculate normal for vertex %d at (%f %f %f)\n", vertIndex, bsp->dvertexes[vertIndex].point[0], bsp->dvertexes[vertIndex].point[1], bsp->dvertexes[vertIndex].point[2]); #endif pair.second = f_norm; } else { pair.second = qv::normalize(vertNormal); } } // sanity check if (!neighboursToSmooth.size()) { for (auto vertIndexNormalPair : smoothedNormals) { Q_assert(GLMVectorCompare(vertIndexNormalPair.second, f_norm, EQUAL_EPSILON)); } } // now, record all of the smoothed normals that are actually part of `f` for (int j=0; jnumedges; j++) { int v = Face_VertexAtIndex(bsp, f, j); Q_assert(smoothedNormals.find(v) != smoothedNormals.end()); vertex_normals[f].push_back(smoothedNormals[v]); } } FaceCache = MakeFaceCache(bsp); } const face_cache_t &FaceCacheForFNum(int fnum) { Q_assert(s_builtPhongCaches); return FaceCache.at(fnum); }