diff --git a/include/common/bspfile.h b/include/common/bspfile.h index d7b632ad..b8d41949 100644 --- a/include/common/bspfile.h +++ b/include/common/bspfile.h @@ -211,7 +211,8 @@ typedef struct texinfo_s { } texinfo_t; #define TEX_SPECIAL 1 /* sky or slime, no lightmap or 256 subdivision */ -#define TEX_CURVED 0x800 /* surface is meant to have smoothed lighting, to hide angular edges, giving a curved appearance */ +#define TEX_PHONG_ANGLE_SHIFT 11 +#define TEX_PHONG_ANGLE_MASK (255U << TEX_PHONG_ANGLE_SHIFT) /* 8 bit value. if non zero, enables phong shading and gives the angle threshold to use. */ /* * Note that edge 0 is never used, because negative edge nums are used for diff --git a/include/light/light.h b/include/light/light.h index 20a16a14..c7bdc0f5 100644 --- a/include/light/light.h +++ b/include/light/light.h @@ -180,6 +180,9 @@ const modelinfo_t *ModelInfoForFace(const bsp2_t *bsp, int facenum); void LightFace(bsp2_dface_t *face, facesup_t *facesup, const modelinfo_t *modelinfo, struct ltface_ctx *ctx); void MakeTnodes(const bsp2_t *bsp); +/* access the final phong-shaded vertex normal */ +const vec_t *GetSurfaceVertexNormal(const bsp2_t *bsp, const bsp2_dface_t *f, const int v); + extern float scaledist; extern float rangescale; extern float anglescale; diff --git a/light/light.cc b/light/light.cc index 67092ccc..82ffc136 100644 --- a/light/light.cc +++ b/light/light.cc @@ -18,10 +18,15 @@ */ #include +#include #include #include +#include +#include +#include + float scaledist = 1.0; float rangescale = 0.5; float anglescale = 0.5; @@ -62,7 +67,6 @@ qboolean testFenceTextures = false; qboolean surflight_dump = false; static facesup_t *faces_sup; //lit2/bspx stuff -vec3_t *vertex_normals; byte *filebase; // start of lightmap data static byte *file_p; // start of free space after data @@ -300,9 +304,20 @@ AngleBetweenPoints(const vec3_t p1, const vec3_t p2, const vec3_t p3) return result; } -/* given a triangle, just adds the contribution from the triangle to the given vertexes normals, based upon angles at the verts */ +class vec3_struct_t { +public: + vec3_t v; + vec3_struct_t() { + VectorSet(v, 0, 0, 0); + } +}; + +std::map> vertex_normals; + +/* 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(const vec_t *norm, const dvertex_t *verts, int v1, int v2, int v3) +AddTriangleNormals(std::map &smoothed_normals, const vec_t *norm, const dvertex_t *verts, int v1, int v2, int v3) { const vec_t *p1 = verts[v1].point; const vec_t *p2 = verts[v2].point; @@ -310,13 +325,13 @@ AddTriangleNormals(const vec_t *norm, const dvertex_t *verts, int v1, int v2, in float weight; weight = AngleBetweenPoints(p2, p1, p3); - VectorMA(vertex_normals[v1], weight, norm, vertex_normals[v1]); + VectorMA(smoothed_normals[v1].v, weight, norm, smoothed_normals[v1].v); weight = AngleBetweenPoints(p1, p2, p3); - VectorMA(vertex_normals[v2], weight, norm, vertex_normals[v2]); + VectorMA(smoothed_normals[v2].v, weight, norm, smoothed_normals[v2].v); weight = AngleBetweenPoints(p1, p3, p2); - VectorMA(vertex_normals[v3], weight, norm, vertex_normals[v3]); + VectorMA(smoothed_normals[v3].v, weight, norm, smoothed_normals[v3].v); } /* small helper that just retrieves the correct vertex from face->surfedge->edge lookups */ static int GetSurfaceVertex(const bsp2_t *bsp, const bsp2_dface_t *f, int v) @@ -328,41 +343,127 @@ static int GetSurfaceVertex(const bsp2_t *bsp, const bsp2_dface_t *f, int v) return bsp->dedges[edge].v[0]; } +static void +Face_Normal(const bsp2_t *bsp, const bsp2_dface_t *f, vec3_t norm) +{ + if (f->side) + VectorSubtract(vec3_origin, bsp->dplanes[f->planenum].normal, norm); + else + VectorCopy(bsp->dplanes[f->planenum].normal, norm); +} + +const vec_t *GetSurfaceVertexNormal(const bsp2_t *bsp, const bsp2_dface_t *f, const int vertindex) +{ + const auto &face_normals_vector = vertex_normals[f]; + return face_normals_vector[vertindex].v; +} + static void CalcualateVertexNormals(const bsp2_t *bsp) { - int i, j, v1, v2, v3; - bsp2_dface_t *f; - vec3_t norm; - - vertex_normals = (vec3_t *)malloc(sizeof(vec3_t) * bsp->numvertexes); - memset(vertex_normals, 0, sizeof(vec3_t) * bsp->numvertexes); - - for (i = 0; i < bsp->numfaces; i++) - { - f = &bsp->dfaces[i]; - if (!(bsp->texinfo[f->texinfo].flags & TEX_CURVED)) /* we only care about smoothed faces. unsmoothed stuff shouldn't blend. */ - continue; - - if (f->side) - VectorSubtract(vec3_origin, bsp->dplanes[f->planenum].normal, norm); - else - VectorCopy(bsp->dplanes[f->planenum].normal, norm); - - /* now just walk around the surface as a triangle fan */ - v1 = GetSurfaceVertex(bsp, f, 0); - v2 = GetSurfaceVertex(bsp, f, 1); - for (j = 2; j < f->numedges; j++) - { - v3 = GetSurfaceVertex(bsp, f, j); - AddTriangleNormals(norm, bsp->dvertexes, v1, v2, v3); - v2 = v3; + // build "vert index -> faces" map + std::map> vertsToFaces; + for (int i = 0; i < bsp->numfaces; i++) { + const bsp2_dface_t *f = &bsp->dfaces[i]; + for (int j = 0; j < f->numedges; j++) { + const int v = GetSurfaceVertex(bsp, f, j); + vertsToFaces[v].push_back(f); } } + + // build the "face -> faces to smooth with" map + std::map> smoothFaces; + for (int i = 0; i < bsp->numfaces; i++) { + bsp2_dface_t *f = &bsp->dfaces[i]; + + vec3_t f_norm; + Face_Normal(bsp, f, f_norm); + + // any face normal within this many degrees can be smoothed with this face + const int f_smoothangle = (bsp->texinfo[f->texinfo].flags & TEX_PHONG_ANGLE_MASK) >> TEX_PHONG_ANGLE_SHIFT; + if (!f_smoothangle) + continue; + + for (int j = 0; j < f->numedges; j++) { + const int v = GetSurfaceVertex(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 = (bsp->texinfo[f2->texinfo].flags & TEX_PHONG_ANGLE_MASK) >> TEX_PHONG_ANGLE_SHIFT; + if (!f2_smoothangle) + continue; + + vec3_t f2_norm; + Face_Normal(bsp, f2, f2_norm); - for (i = 0; i < bsp->numvertexes; i++) + const vec_t angle = acos(DotProduct(f_norm, f2_norm)); + const vec_t max_angle = DEG2RAD(qmin(f_smoothangle, f2_smoothangle)); + + // check the angle between the face normals + if (angle < max_angle) { + smoothFaces[f].insert(f2); + } + } + } + } + + // finally do the smoothing + for (int i = 0; i < bsp->numfaces; i++) { - VectorNormalize(vertex_normals[i]); + const bsp2_dface_t *f = &bsp->dfaces[i]; + const auto &neighboursToSmooth = smoothFaces[f]; + + // 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) { + vec3_t f2_norm; + Face_Normal(bsp, f2, f2_norm); + + /* now just walk around the surface as a triangle fan */ + int v1, v2, v3; + v1 = GetSurfaceVertex(bsp, f2, 0); + v2 = GetSurfaceVertex(bsp, f2, 1); + for (int j = 2; j < f2->numedges; j++) + { + v3 = GetSurfaceVertex(bsp, f2, j); + AddTriangleNormals(smoothedNormals, f2_norm, bsp->dvertexes, v1, v2, v3); + v2 = v3; + } + } + + for (auto &vertIndexNormalPair : smoothedNormals) + { + VectorNormalize(vertIndexNormalPair.second.v); + } + + // sanity check + if (!neighboursToSmooth.size()) { + vec3_t f_norm; + Face_Normal(bsp, f, f_norm); + for (auto vertIndexNormalPair : smoothedNormals) + { + assert(VectorCompare(vertIndexNormalPair.second.v, f_norm)); + } + } + + // now, record all of the smoothed normals that are actually part of `f` + for (int j=0; jnumedges; j++) { + int v = GetSurfaceVertex(bsp, f, j); + assert(smoothedNormals.find(v) != smoothedNormals.end()); + + vertex_normals[f].push_back(smoothedNormals[v]); + } } } diff --git a/light/ltface.c b/light/ltface.c index 485c6a80..a2ce27f4 100644 --- a/light/ltface.c +++ b/light/ltface.c @@ -20,7 +20,6 @@ #include #include -extern vec3_t *vertex_normals; extern unsigned int lightturb; /* ======================================================================== */ @@ -427,10 +426,6 @@ vec_t *GetSurfaceVertexPoint(const bsp2_t *bsp, const bsp2_dface_t *f, int v) { return bsp->dvertexes[GetSurfaceVertex(bsp, f, v)].point; } -vec_t *GetSurfaceVertexNormal(const bsp2_t *bsp, const bsp2_dface_t *f, int v) -{ - return vertex_normals[GetSurfaceVertex(bsp, f, v)]; -} static int ClipPointToTriangle(const vec_t *orig, vec_t *point, const vec_t *norm_, const vec_t *v1, const vec_t *v2, const vec_t *v3) @@ -518,7 +513,7 @@ static void CalcPointNormal(const bsp2_t *bsp, const bsp2_dface_t *face, plane_t #if 1 int j; - vec_t *v1, *v2, *v3; + const vec_t *v1, *v2, *v3; int best; //3rd point vec3_t clipped, t; // vec3_t bestp = {point[0],point[1],point[2]}; @@ -729,7 +724,7 @@ Lightsurf_Init(const modelinfo_t *modelinfo, const bsp2_dface_t *face, else lightsurf->lightmapscale = modelinfo->lightmapscale; - if (bsp->texinfo[face->texinfo].flags & TEX_CURVED) + if (bsp->texinfo[face->texinfo].flags & TEX_PHONG_ANGLE_MASK) lightsurf->curved = true; else lightsurf->curved = false; diff --git a/qbsp/map.c b/qbsp/map.c index 62ee45b7..0e19002b 100644 --- a/qbsp/map.c +++ b/qbsp/map.c @@ -201,8 +201,23 @@ FindTexinfoEnt(texinfo_t *texinfo, mapentity_t *entity) flags |= TEX_HINT; if (IsSplitName(texname)) flags |= TEX_SPECIAL; - if (entity->smoothedtexture && !Q_strcasecmp(texname, entity->smoothedtexture->value)) - flags |= TEX_CURVED; + + vec_t phongangle = atof(ValueForKey(entity, "_phong_angle")); + int phong = atoi(ValueForKey(entity, "_phong")); + + if (phong && (phongangle == 0.0)) { + phongangle = 89.0; // default _phong_angle + } + + if (phongangle) { + int phongangle_int = rint(phongangle); + + if (phongangle_int < 0) phongangle_int = 0; + if (phongangle_int > 255) phongangle_int = 255; + + flags |= (phongangle_int << TEX_PHONG_ANGLE_SHIFT); + } + return FindTexinfo(texinfo, flags); } @@ -226,8 +241,6 @@ ParseEpair(parser_t *parser, mapentity_t *entity) if (!Q_strcasecmp(epair->key, "origin")) { GetVectorForKey(entity, epair->key, entity->origin); - } else if (!Q_strcasecmp(epair->key, "_smooth")) { - entity->smoothedtexture = epair; } else if (!Q_strcasecmp(epair->key, "classname")) { if (!Q_strcasecmp(epair->value, "info_player_start")) { if (rgfStartSpots & info_player_start) @@ -638,7 +651,6 @@ ParseEntity(parser_t *parser, mapentity_t *entity) if (map.numentities == map.maxentities) Error("Internal error: didn't allocate enough entities?"); - entity->smoothedtexture = NULL; entity->mapbrushes = brush = map.brushes + map.numbrushes; do { if (!ParseToken(parser, PARSE_NORMAL)) diff --git a/qbsp/qbsp.h b/qbsp/qbsp.h index 68708f10..dd604a67 100644 --- a/qbsp/qbsp.h +++ b/qbsp/qbsp.h @@ -113,7 +113,9 @@ #define TEX_SPECIAL (1U << 0) /* sky or liquid (no lightmap or subdivision */ #define TEX_SKIP (1U << 1) /* an invisible surface */ #define TEX_HINT (1U << 2) /* hint surface */ -#define TEX_CURVED (1U << 11) /* surface is meant to have smoothed lighting, to hide angular edges, giving a curved appearance */ + +#define TEX_PHONG_ANGLE_SHIFT 11 +#define TEX_PHONG_ANGLE_MASK (255U << TEX_PHONG_ANGLE_SHIFT) /* 8 bit value. if non zero, enables phong shading and gives the angle threshold to use. */ /* * The quality of the bsp output is highly sensitive to these epsilon values. @@ -514,7 +516,6 @@ struct lumpdata { typedef struct mapentity_s { vec3_t origin; - epair_t *smoothedtexture; mapbrush_t *mapbrushes; /* Array */ int nummapbrushes; epair_t *epairs; diff --git a/qbsp/writebsp.c b/qbsp/writebsp.c index 39db5a4b..5de7f7d4 100644 --- a/qbsp/writebsp.c +++ b/qbsp/writebsp.c @@ -707,7 +707,7 @@ CleanBSPTexinfoFlags(void) int i; for (i = 0; i < num_texinfo; i++, texinfo++) - texinfo->flags &= TEX_SPECIAL|TEX_CURVED; + texinfo->flags &= TEX_SPECIAL|TEX_PHONG_ANGLE_MASK; // still write TEX_PHONG_ANGLE_MASK } /*