From b6ea17e7e0f23d6f744b9233066703c79c4eeb30 Mon Sep 17 00:00:00 2001 From: fau Date: Thu, 4 May 2023 00:19:50 +0200 Subject: [PATCH 1/2] Improve glass shatter effect Fixes bugs where glass shards would not match the glass entity frame or extend past it while preserving original idea for the algorithm --- src/renderer/tr_world.cpp | 201 ++++++++++++++++++++++++++------------ 1 file changed, 140 insertions(+), 61 deletions(-) diff --git a/src/renderer/tr_world.cpp b/src/renderer/tr_world.cpp index f28b25c7f..9d59e571f 100644 --- a/src/renderer/tr_world.cpp +++ b/src/renderer/tr_world.cpp @@ -317,39 +317,33 @@ void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { } } -float GetQuadArea( vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4 ) +static float GetQuadArea( const vec3_t v[4], const vec3_t normal ) { - vec3_t vec1, vec2, dis1, dis2; + // v are quad corners in clockwise order. When they don't form + // convex quad, area will be negative. + vec3_t vec1, vec2, dis1, dis2, area; - // Get area of tri1 - VectorSubtract( v1, v2, vec1 ); - VectorSubtract( v1, v4, vec2 ); + // area of v0, v1, v3 tri (may be negative when v[0] is concave) + VectorSubtract( v[3], v[0], vec1 ); + VectorSubtract( v[1], v[0], vec2 ); CrossProduct( vec1, vec2, dis1 ); - VectorScale( dis1, 0.25f, dis1 ); - // Get area of tri2 - VectorSubtract( v3, v2, vec1 ); - VectorSubtract( v3, v4, vec2 ); + // area of v1, v2, v3 tri (may be negative when v[2] is concave) + VectorSubtract( v[1], v[2], vec1 ); + VectorSubtract( v[3], v[2], vec2 ); CrossProduct( vec1, vec2, dis2 ); - VectorScale( dis2, 0.25f, dis2 ); - // Return addition of disSqr of each tri area - return ( dis1[0] * dis1[0] + dis1[1] * dis1[1] + dis1[2] * dis1[2] + - dis2[0] * dis2[0] + dis2[1] * dis2[1] + dis2[2] * dis2[2] ); + // dis1, dis2 are colinear because quad points are on a single + // plane + VectorAdd(dis1, dis2, area); + + return DotProduct(normal, area); } void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) { - msurface_t *surfs; - srfSurfaceFace_t *face; bmodel_t *bmodel; model_t *pModel; - int i; - // Not sure if we really need to track the best two candidates - int maxDist[2]={0,0}; - int maxIndx[2]={0,0}; - int dist = 0; - float dot1, dot2; pModel = R_GetModelByHandle( bmodelIndex ); bmodel = pModel->bmodel; @@ -364,64 +358,149 @@ void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) ri.Error(ERR_DROP, "RE_GetBModelVerts: model has no surfaces"); } - // Loop through all surfaces on the brush and find the best two candidates - for ( i = 0 ; i < bmodel->numSurfaces; i++ ) + // Group surfaces laying on the same plane + int *surfaceGrp = (int *)ri.Hunk_AllocateTempMemory(bmodel->numSurfaces * sizeof(int)); + + for (int i = 0; i < bmodel->numSurfaces; i++) + surfaceGrp[i] = -1; + + int grpNum = 0; + for (int i = 0; i < bmodel->numSurfaces; i++) { - surfs = bmodel->firstSurface + i; - face = ( srfSurfaceFace_t *)surfs->data; + if (surfaceGrp[i] != -1) + continue; - // It seems that the safest way to handle this is by finding the area of the faces - dist = GetQuadArea( face->points[0], face->points[1], face->points[2], face->points[3] ); + surfaceGrp[i] = grpNum; - // Check against the highest max - if ( dist > maxDist[0] ) - { - // Shuffle our current maxes down - maxDist[1] = maxDist[0]; - maxIndx[1] = maxIndx[0]; + msurface_t *surf = bmodel->firstSurface + i; + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; - maxDist[0] = dist; - maxIndx[0] = i; - } - // Check against the second highest max - else if ( dist >= maxDist[1] ) + for (int j = i + 1; j < bmodel->numSurfaces; j++) { - // just stomp the old - maxDist[1] = dist; - maxIndx[1] = i; + if (surfaceGrp[j] != -1) + continue; + + msurface_t *jsurf = bmodel->firstSurface + j; + srfSurfaceFace_t *jface = (srfSurfaceFace_t *)jsurf->data; + + // that's a pretty big epsilon but 0.01f was not enough for duel_training + if (fabsf(face->plane.dist - jface->plane.dist) < 0.1f && + DistanceSquared(face->plane.normal, jface->plane.normal) < 0.001f * 0.001f) + { + surfaceGrp[j] = grpNum; + } } + + grpNum++; } - // Hopefully we've found two best case candidates. Now we should see which of these faces the viewer - surfs = bmodel->firstSurface + maxIndx[0]; - face = ( srfSurfaceFace_t *)surfs->data; - dot1 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + // Calculate total camera-facing area of each surface group + float *grpArea = (float *)ri.Hunk_AllocateTempMemory(grpNum * sizeof(float)); - surfs = bmodel->firstSurface + maxIndx[1]; - face = ( srfSurfaceFace_t *)surfs->data; - dot2 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + for (int i = 0; i < grpNum; i++) + grpArea[i] = 0.0f; - if ( dot2 < dot1 && dot2 < 0.0f ) + for (int i = 0; i < bmodel->numSurfaces; i++) { - i = maxIndx[1]; // use the second face + msurface_t *surf = bmodel->firstSurface + i; + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; + unsigned *indices = (unsigned *)(((char *)face) + face->ofsIndices ); + int numTris = face->numIndices / 3; + float faceArea = 0.0f; + + // Calculate area of face + for (int j = 0; j < numTris; j++) { + float *v1 = face->points[indices[3 * j + 0]]; + float *v2 = face->points[indices[3 * j + 1]]; + float *v3 = face->points[indices[3 * j + 2]]; + vec3_t vec1, vec2, cross; + + // Get area of tri (times 2) + VectorSubtract(v1, v2, vec1); + VectorSubtract(v1, v3, vec2); + CrossProduct(vec1, vec2, cross); + faceArea += VectorLength(cross); + } + + // Calculate camera-facing area (with sign) and add to total + grpArea[surfaceGrp[i]] -= faceArea * DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); } - else if ( dot1 < dot2 && dot1 < 0.0f ) + + int bestGrp = 0; + for (int i = 1; i < grpNum; i++) { - i = maxIndx[0]; // use the first face - } - else - { // Possibly only have one face, so may as well use the first face, which also should be the best one - //i = rand() & 1; // ugh, we don't know which to use. I'd hope this would never happen - i = maxIndx[0]; // use the first face + if (grpArea[i] > grpArea[bestGrp]) + bestGrp = i; } - surfs = bmodel->firstSurface + i; - face = ( srfSurfaceFace_t *)surfs->data; + ri.Hunk_FreeTempMemory(grpArea); - for ( int t = 0; t < 4; t++ ) + // Now find best quad surface representing the group. The + // algorithm makes assumption that combined surfaces within a + // group (on the same plane) are roughly the shape of a single + // convex quad. No holes or unconnected parts allowed. + vec3_t qverts[4]; + float quadArea; + qboolean seeded = qfalse; + + for (int i = 0; i < bmodel->numSurfaces; i++) { - VectorCopy( face->points[t], verts[t] ); + if (surfaceGrp[i] != bestGrp) + continue; + + msurface_t *surf = bmodel->firstSurface + i; + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; + + // seed with empty quad + if (!seeded) { + seeded = qtrue; + VectorCopy(face->points[0], qverts[0]); + VectorCopy(face->points[1], qverts[1]); + VectorCopy(face->points[2], qverts[2]); + VectorCopy(face->points[2], qverts[3]); // degenerate + quadArea = GetQuadArea(qverts, face->plane.normal); + if (quadArea < 0.0f) { + // order qverts so that quadArea is positive + VectorCopy(face->points[2], qverts[1]); + VectorCopy(face->points[1], qverts[2]); + quadArea = - quadArea; + } + } + + for (int j = 0; j < face->numPoints; j++) { + // Try to replace each corner in quad with surface point + // and see if quad area increased. For arbitrary convex + // surface result would depend heavily on initial seeding + // point, but under assumption that surface resembles quad + // result should be the same or very similar regardless + // where we start and cover the quad. + int bestCorner = -1; + int bestArea = quadArea; + for (int k = 0; k < 4; k++) { + vec3_t temp; + float area; + VectorCopy(qverts[k], temp); + VectorCopy(face->points[j], qverts[k]); + area = GetQuadArea(qverts, face->plane.normal); + if (area > bestArea) { + bestCorner = k; + bestArea = area; + } + VectorCopy(temp, qverts[k]); + } + + if (bestCorner != -1) { + quadArea = bestArea; + VectorCopy(face->points[j], qverts[bestCorner]); + } + } } + + for (int i = 0; i < 4; i++) { + VectorCopy(qverts[i], verts[i]); + } + + ri.Hunk_FreeTempMemory(surfaceGrp); } /* From cb1d53c566ec600a2b64b048d822c0abee3b6d76 Mon Sep 17 00:00:00 2001 From: fau Date: Thu, 4 May 2023 00:46:48 +0200 Subject: [PATCH 2/2] Select glass shatter surface based on area first, orientation later --- src/renderer/tr_world.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/renderer/tr_world.cpp b/src/renderer/tr_world.cpp index 9d59e571f..ce1e1cfd4 100644 --- a/src/renderer/tr_world.cpp +++ b/src/renderer/tr_world.cpp @@ -395,10 +395,10 @@ void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) } // Calculate total camera-facing area of each surface group - float *grpArea = (float *)ri.Hunk_AllocateTempMemory(grpNum * sizeof(float)); + float *grpScore = (float *)ri.Hunk_AllocateTempMemory(grpNum * sizeof(float)); for (int i = 0; i < grpNum; i++) - grpArea[i] = 0.0f; + grpScore[i] = 0.0f; for (int i = 0; i < bmodel->numSurfaces; i++) { @@ -422,18 +422,21 @@ void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) faceArea += VectorLength(cross); } - // Calculate camera-facing area (with sign) and add to total - grpArea[surfaceGrp[i]] -= faceArea * DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + // Score gives precedence to surfaces oriented towards camera, + // but when there is a big difference in area size (area) + // orientation becomes irrelevant and bigger surface will be + // selected. + grpScore[surfaceGrp[i]] += faceArea * (1.0f - 0.1f * DotProduct(face->plane.normal, tr.refdef.viewaxis[0])); } int bestGrp = 0; for (int i = 1; i < grpNum; i++) { - if (grpArea[i] > grpArea[bestGrp]) + if (grpScore[i] > grpScore[bestGrp]) bestGrp = i; } - ri.Hunk_FreeTempMemory(grpArea); + ri.Hunk_FreeTempMemory(grpScore); // Now find best quad surface representing the group. The // algorithm makes assumption that combined surfaces within a