From 815720f4db1fa519c89077c74c904908b1072a6f Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 18 Aug 2022 04:11:04 +0200 Subject: [PATCH 1/4] Switch actors from using light probes to performing trace visibility tests --- src/playsim/a_dynlight.h | 5 +- src/r_data/gldefs.cpp | 17 ++++ .../hwrenderer/scene/hw_spritelight.cpp | 86 +++++++++++-------- src/rendering/hwrenderer/scene/hw_sprites.cpp | 7 -- src/rendering/hwrenderer/scene/hw_weapon.cpp | 7 -- wadsrc/static/mapinfo/common.txt | 4 +- .../static/zscript/actors/shared/dynlights.zs | 21 +++++ 7 files changed, 95 insertions(+), 52 deletions(-) diff --git a/src/playsim/a_dynlight.h b/src/playsim/a_dynlight.h index 586c9c799..9214d8ff4 100644 --- a/src/playsim/a_dynlight.h +++ b/src/playsim/a_dynlight.h @@ -42,7 +42,8 @@ enum LightFlag LF_DONTLIGHTACTORS = 32, LF_SPOT = 64, LF_DONTLIGHTOTHERS = 128, - LF_DONTLIGHTMAP = 256 + LF_DONTLIGHTMAP = 256, + LF_TRACE = 512 }; typedef TFlags LightFlags; @@ -76,6 +77,7 @@ public: void SetDontLightActors(bool on) { if (on) m_lightFlags |= LF_DONTLIGHTACTORS; else m_lightFlags &= ~LF_DONTLIGHTACTORS; } void SetDontLightOthers(bool on) { if (on) m_lightFlags |= LF_DONTLIGHTOTHERS; else m_lightFlags &= ~LF_DONTLIGHTOTHERS; } void SetDontLightMap(bool on) { if (on) m_lightFlags |= LF_DONTLIGHTMAP; else m_lightFlags &= ~LF_DONTLIGHTMAP; } + void SetTrace(bool on) { if (on) m_lightFlags |= LF_TRACE; else m_lightFlags &= ~LF_TRACE; } void SetNoShadowmap(bool on) { if (on) m_lightFlags |= LF_NOSHADOWMAP; else m_lightFlags &= ~LF_NOSHADOWMAP; } void SetSpot(bool spot) { if (spot) m_lightFlags |= LF_SPOT; else m_lightFlags &= ~LF_SPOT; } void SetSpotInnerAngle(double angle) { m_spotInnerAngle = angle; } @@ -230,6 +232,7 @@ struct FDynamicLight bool IsAdditive() const { return !!((*pLightFlags) & LF_ADDITIVE); } bool IsSpot() const { return !!((*pLightFlags) & LF_SPOT); } bool IsAttenuated() const { return !!((*pLightFlags) & LF_ATTENUATE); } + bool Trace() const { return !!((*pLightFlags) & (LF_TRACE)); } bool DontShadowmap() const { return !!((*pLightFlags) & LF_NOSHADOWMAP); } bool DontLightSelf() const { return !!((*pLightFlags) & (LF_DONTLIGHTSELF|LF_DONTLIGHTACTORS)); } // dontlightactors implies dontlightself. bool DontLightActors() const { return !!((*pLightFlags) & LF_DONTLIGHTACTORS); } diff --git a/src/r_data/gldefs.cpp b/src/r_data/gldefs.cpp index 4218c3f0e..aac9c06df 100644 --- a/src/r_data/gldefs.cpp +++ b/src/r_data/gldefs.cpp @@ -191,6 +191,7 @@ static const char *LightTags[]= "noshadowmap", "dontlightothers", "dontlightmap", + "trace", nullptr }; @@ -217,6 +218,7 @@ enum { LIGHTTAG_NOSHADOWMAP, LIGHTTAG_DONTLIGHTOTHERS, LIGHTTAG_DONTLIGHTMAP, + LIGHTTAG_TRACE, }; //========================================================================== @@ -523,6 +525,9 @@ class GLDefsParser case LIGHTTAG_DONTLIGHTMAP: defaults->SetDontLightMap(ParseInt(sc) != 0); break; + case LIGHTTAG_TRACE: + defaults->SetTrace(ParseInt(sc) != 0); + break; case LIGHTTAG_SPOT: { float innerAngle = ParseFloat(sc); @@ -625,6 +630,9 @@ class GLDefsParser case LIGHTTAG_DONTLIGHTMAP: defaults->SetDontLightMap(ParseInt(sc) != 0); break; + case LIGHTTAG_TRACE: + defaults->SetTrace(ParseInt(sc) != 0); + break; case LIGHTTAG_SPOT: { float innerAngle = ParseFloat(sc); @@ -730,6 +738,9 @@ class GLDefsParser case LIGHTTAG_DONTLIGHTMAP: defaults->SetDontLightMap(ParseInt(sc) != 0); break; + case LIGHTTAG_TRACE: + defaults->SetTrace(ParseInt(sc) != 0); + break; case LIGHTTAG_SPOT: { float innerAngle = ParseFloat(sc); @@ -834,6 +845,9 @@ class GLDefsParser case LIGHTTAG_DONTLIGHTMAP: defaults->SetDontLightMap(ParseInt(sc) != 0); break; + case LIGHTTAG_TRACE: + defaults->SetTrace(ParseInt(sc) != 0); + break; case LIGHTTAG_SPOT: { float innerAngle = ParseFloat(sc); @@ -935,6 +949,9 @@ class GLDefsParser case LIGHTTAG_DONTLIGHTMAP: defaults->SetDontLightMap(ParseInt(sc) != 0); break; + case LIGHTTAG_TRACE: + defaults->SetTrace(ParseInt(sc) != 0); + break; case LIGHTTAG_SPOT: { float innerAngle = ParseFloat(sc); diff --git a/src/rendering/hwrenderer/scene/hw_spritelight.cpp b/src/rendering/hwrenderer/scene/hw_spritelight.cpp index f9998398a..64f98f650 100644 --- a/src/rendering/hwrenderer/scene/hw_spritelight.cpp +++ b/src/rendering/hwrenderer/scene/hw_spritelight.cpp @@ -101,6 +101,16 @@ LightProbe* FindLightProbe(FLevelLocals* level, float x, float y, float z) return foundprobe; } +static bool TraceLightVisbility(FLightNode* node, const FVector3& L, float dist) +{ + FDynamicLight* light = node->lightsource; + if (!light->Trace()) + return true; + + FTraceResults results; + return !Trace(light->Pos, light->Sector, DVector3(-L.X, -L.Y, -L.Z), dist, 0, ML_BLOCKING, nullptr, results); +} + //========================================================================== // // Sets a single light value from all dynamic lights affecting the specified location @@ -115,14 +125,6 @@ void HWDrawInfo::GetDynSpriteLight(AActor *self, float x, float y, float z, FLig out[0] = out[1] = out[2] = 0.f; - LightProbe* probe = FindLightProbe(Level, x, y, z); - if (probe) - { - out[0] = probe->Red; - out[1] = probe->Green; - out[2] = probe->Blue; - } - // Go through both light lists while (node) { @@ -156,38 +158,43 @@ void HWDrawInfo::GetDynSpriteLight(AActor *self, float x, float y, float z, FLig { dist = sqrtf(dist); // only calculate the square root if we really need it. - frac = 1.0f - (dist / radius); - - if (light->IsSpot()) - { + if (light->IsSpot() || light->Trace()) L *= -1.0f / dist; - DAngle negPitch = -*light->pPitch; - DAngle Angle = light->target->Angles.Yaw; - double xyLen = negPitch.Cos(); - double spotDirX = -Angle.Cos() * xyLen; - double spotDirY = -Angle.Sin() * xyLen; - double spotDirZ = -negPitch.Sin(); - double cosDir = L.X * spotDirX + L.Y * spotDirY + L.Z * spotDirZ; - frac *= (float)smoothstep(light->pSpotOuterAngle->Cos(), light->pSpotInnerAngle->Cos(), cosDir); - } - if (frac > 0 && (!light->shadowmapped || (light->GetRadius() > 0 && screen->mShadowMap.ShadowTest(light->Pos, { x, y, z })))) + if (TraceLightVisbility(node, L, dist)) { - lr = light->GetRed() / 255.0f; - lg = light->GetGreen() / 255.0f; - lb = light->GetBlue() / 255.0f; - if (light->IsSubtractive()) + frac = 1.0f - (dist / radius); + + if (light->IsSpot()) { - float bright = (float)FVector3(lr, lg, lb).Length(); - FVector3 lightColor(lr, lg, lb); - lr = (bright - lr) * -1; - lg = (bright - lg) * -1; - lb = (bright - lb) * -1; + DAngle negPitch = -*light->pPitch; + DAngle Angle = light->target->Angles.Yaw; + double xyLen = negPitch.Cos(); + double spotDirX = -Angle.Cos() * xyLen; + double spotDirY = -Angle.Sin() * xyLen; + double spotDirZ = -negPitch.Sin(); + double cosDir = L.X * spotDirX + L.Y * spotDirY + L.Z * spotDirZ; + frac *= (float)smoothstep(light->pSpotOuterAngle->Cos(), light->pSpotInnerAngle->Cos(), cosDir); } - out[0] += lr * frac; - out[1] += lg * frac; - out[2] += lb * frac; + if (frac > 0 && (!light->shadowmapped || (light->GetRadius() > 0 && screen->mShadowMap.ShadowTest(light->Pos, { x, y, z })))) + { + lr = light->GetRed() / 255.0f; + lg = light->GetGreen() / 255.0f; + lb = light->GetBlue() / 255.0f; + if (light->IsSubtractive()) + { + float bright = (float)FVector3(lr, lg, lb).Length(); + FVector3 lightColor(lr, lg, lb); + lr = (bright - lr) * -1; + lg = (bright - lg) * -1; + lb = (bright - lb) * -1; + } + + out[0] += lr * frac; + out[1] += lg * frac; + out[2] += lb * frac; + } } } } @@ -249,7 +256,16 @@ void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata) { if (std::find(addedLights.begin(), addedLights.end(), light) == addedLights.end()) // Check if we already added this light from a different subsector { - AddLightToList(modellightdata, group, light, true); + FVector3 L(dx, dy, dz); + float dist = sqrtf(distSquared); + if (light->Trace()) + L *= 1.0f / dist; + + if (TraceLightVisbility(node, L, dist)) + { + AddLightToList(modellightdata, group, light, true); + } + addedLights.Push(light); } } diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 3e44171db..5947f2f99 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -294,13 +294,6 @@ void HWSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent) } else { - if (actor && di->Level->LightProbes.Size() > 0) - { - LightProbe* probe = FindLightProbe(di->Level, actor->X(), actor->Y(), actor->Center()); - if (probe) - state.SetDynLight(probe->Red, probe->Green, probe->Blue); - } - FHWModelRenderer renderer(di, state, dynlightindex); RenderModel(&renderer, x, y, z, modelframe, actor, di->Viewpoint.TicFrac); state.SetVertexBuffer(screen->mVertexData); diff --git a/src/rendering/hwrenderer/scene/hw_weapon.cpp b/src/rendering/hwrenderer/scene/hw_weapon.cpp index 5125b41b9..579c0e3b2 100644 --- a/src/rendering/hwrenderer/scene/hw_weapon.cpp +++ b/src/rendering/hwrenderer/scene/hw_weapon.cpp @@ -646,13 +646,6 @@ void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area) { hw_GetDynModelLight(playermo, lightdata); hudsprite.lightindex = screen->mLights->UploadLights(lightdata); - LightProbe* probe = FindLightProbe(playermo->Level, playermo->X(), playermo->Y(), playermo->Center()); - if (probe) - { - hudsprite.dynrgb[0] = probe->Red; - hudsprite.dynrgb[1] = probe->Green; - hudsprite.dynrgb[2] = probe->Blue; - } } } diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index 6982a5bce..a67e54722 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -137,8 +137,8 @@ DoomEdNums 9873 = SectorSpotLightAttenuated 9874 = SpotLightFlickerRandomAttenuated 9875 = None // ZDRay light probe - 9876 = None // ZDRay static point light - 9881 = None // ZDRay static spotlight + 9876 = PointLightTraceAttenuated // ZDRay static point light + 9881 = SpotLightTraceAttenuated // ZDRay static spotlight 9890 = None // ZDRayInfo 9982 = SecActEyesAboveC 9983 = SecActEyesBelowC diff --git a/wadsrc/static/zscript/actors/shared/dynlights.zs b/wadsrc/static/zscript/actors/shared/dynlights.zs index 8c5d0850d..132c78fd0 100644 --- a/wadsrc/static/zscript/actors/shared/dynlights.zs +++ b/wadsrc/static/zscript/actors/shared/dynlights.zs @@ -17,6 +17,7 @@ class DynamicLight : Actor flagdef spot: lightflags, 6; flagdef dontlightothers: lightflags, 7; flagdef dontlightmap: lightflags, 8; + flagdef trace: lightflags, 9; enum EArgs { @@ -40,6 +41,7 @@ class DynamicLight : Actor LF_SPOT = 64, LF_DONTLIGHTOTHERS = 128, LF_DONTLIGHTMAP = 256, + LF_TRACE = 512 }; enum ELightType @@ -440,6 +442,25 @@ class SpotLightFlickerRandomAttenuated : SpotLightFlickerRandom } } +class PointLightTraceAttenuated : PointLightAttenuated +{ + Default + { + +DYNAMICLIGHT.TRACE + +DYNAMICLIGHT.DONTLIGHTMAP + } +} + +class SpotLightTraceAttenuated : SpotLightAttenuated +{ + Default + { + +DYNAMICLIGHT.TRACE + +DYNAMICLIGHT.DONTLIGHTMAP + } +} + + class VavoomLight : DynamicLight { Default From 218e43984b3ef9db28c2ba62e8c1f068696d9551 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 18 Aug 2022 17:16:37 +0200 Subject: [PATCH 2/4] Add missing reset of LightIndex after use --- src/rendering/hwrenderer/scene/hw_sprites.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 5947f2f99..09e47094b 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -297,6 +297,7 @@ void HWSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent) FHWModelRenderer renderer(di, state, dynlightindex); RenderModel(&renderer, x, y, z, modelframe, actor, di->Viewpoint.TicFrac); state.SetVertexBuffer(screen->mVertexData); + state.SetLightIndex(-1); } } From d3ed7be087bd729c362f8267fd25ec2f0e8b36b1 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 21 Sep 2022 22:43:43 +0200 Subject: [PATCH 3/4] Remove light probes completely and make decals use the lightmap --- src/d_main.cpp | 4 +- src/g_levellocals.h | 7 --- src/gamedata/r_defs.h | 13 ---- src/maploader/maploader.cpp | 63 +------------------ src/rendering/hwrenderer/scene/hw_decal.cpp | 63 +++++++++++++------ .../hwrenderer/scene/hw_drawstructs.h | 2 +- .../hwrenderer/scene/hw_spritelight.cpp | 55 ---------------- 7 files changed, 48 insertions(+), 159 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index df1051af5..70e29266f 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -401,9 +401,9 @@ void D_Render(std::function action, bool interpolate) for (auto Level : AllLevels()) { // Check for the presence of dynamic lights at the start of the frame once. - if ((gl_lights && vid_rendermode == 4) || (r_dynlights && vid_rendermode != 4) || Level->LightProbes.Size() > 0) + if ((gl_lights && vid_rendermode == 4) || (r_dynlights && vid_rendermode != 4) || Level->LMTextureCount > 0) { - Level->HasDynamicLights = Level->lights || Level->LightProbes.Size() > 0; + Level->HasDynamicLights = Level->lights || Level->LMTextureCount > 0; } else Level->HasDynamicLights = false; // lights are off so effectively we have none. if (interpolate) Level->interpolator.DoInterpolations(I_GetTimeFrac()); diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 9953a3e7d..8cd6b3542 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -459,13 +459,6 @@ public: int LMTextureCount = 0; int LMTextureSize = 0; TArray LMTextureData; - TArray LightProbes; - int LPMinX = 0; - int LPMinY = 0; - int LPWidth = 0; - int LPHeight = 0; - static const int LPCellSize = 32; - TArray LPCells; // Portal information. FDisplacementTable Displacements; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index 273ffebfc..947adab89 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -61,7 +61,6 @@ class AActor; struct FSection; struct FLevelLocals; struct LightmapSurface; -struct LightProbe; const uint16_t NO_INDEX = 0xffffu; const uint32_t NO_SIDE = 0xffffffffu; @@ -1706,18 +1705,6 @@ struct LightmapSurface float *TexCoords; }; -struct LightProbe -{ - float X, Y, Z; - float Red, Green, Blue; -}; - -struct LightProbeCell -{ - LightProbe* FirstProbe = nullptr; - int NumProbes = 0; -}; - // // OTHER TYPES // diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 9e4126196..65ad0e2c0 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -3330,17 +3330,11 @@ void MapLoader::SetSideLightmap(const LightmapSurface &surface) void MapLoader::LoadLightmap(MapData *map) { // We have to reset everything as FLevelLocals is recycled between maps - Level->LightProbes.Reset(); - Level->LPCells.Reset(); Level->LMTexCoords.Reset(); Level->LMSurfaces.Reset(); Level->LMTextureData.Reset(); Level->LMTextureCount = 0; Level->LMTextureSize = 0; - Level->LPMinX = 0; - Level->LPMinY = 0; - Level->LPWidth = 0; - Level->LPHeight = 0; if (!Args->CheckParm("-enablelightmaps")) return; // this feature is still too early WIP to allow general access @@ -3379,61 +3373,8 @@ void MapLoader::LoadLightmap(MapData *map) if (numLightProbes > 0) { - Level->LightProbes.Resize(numLightProbes); - fr.Read(&Level->LightProbes[0], sizeof(LightProbe) * numLightProbes); - - // Sort the light probes so that they are ordered by cell. - // This lets us point at the first probe knowing all other probes in the cell will follow. - // Also improves locality. - - double rcpCellSize = 1.0 / Level->LPCellSize; - auto cellCompareLess = [=](const LightProbe& a, const LightProbe& b) - { - double cellY_A = std::floor(a.Y * rcpCellSize); - double cellY_B = std::floor(b.Y * rcpCellSize); - if (cellY_A != cellY_B) - return cellY_A < cellY_B; - double cellX_A = std::floor(a.X * rcpCellSize); - double cellX_B = std::floor(b.X * rcpCellSize); - return cellX_A < cellX_B; - }; - std::sort(Level->LightProbes.begin(), Level->LightProbes.end(), cellCompareLess); - - // Find probe bounds and the grid that covers it - float probesMinX = Level->LightProbes[0].X; - float probesMaxX = Level->LightProbes[0].X; - float probesMinY = Level->LightProbes[0].Y; - float probesMaxY = Level->LightProbes[0].Y; - for (const LightProbe& p : Level->LightProbes) - { - probesMinX = std::min(probesMinX, p.X); - probesMaxX = std::max(probesMaxX, p.X); - probesMinY = std::min(probesMinY, p.Y); - probesMaxY = std::max(probesMaxY, p.Y); - } - Level->LPMinX = (int)std::floor(probesMinX * rcpCellSize); - Level->LPMinY = (int)std::floor(probesMinY * rcpCellSize); - Level->LPWidth = (int)std::floor(probesMaxX * rcpCellSize) + 1 - Level->LPMinX; - Level->LPHeight = (int)std::floor(probesMaxY * rcpCellSize) + 1 - Level->LPMinY; - - // Place probes in a grid for faster search - Level->LPCells.Resize(Level->LPWidth * Level->LPHeight); - int minX = Level->LPMinX; - int minY = Level->LPMinY; - int width = Level->LPWidth; - int height = Level->LPHeight; - for (LightProbe& p : Level->LightProbes) - { - int gridX = (int)std::floor(p.X * rcpCellSize) - minX; - int gridY = (int)std::floor(p.Y * rcpCellSize) - minY; - if (gridX >= 0 && gridY >= 0 && gridX < width && gridY < height) - { - LightProbeCell& cell = Level->LPCells[gridX + (size_t)gridY * width]; - if (!cell.FirstProbe) - cell.FirstProbe = &p; - cell.NumProbes++; - } - } + Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported alpha version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n"); + return; } Level->LMTexCoords.Resize(numTexCoords * 2); diff --git a/src/rendering/hwrenderer/scene/hw_decal.cpp b/src/rendering/hwrenderer/scene/hw_decal.cpp index 980fc52a5..0803e4e00 100644 --- a/src/rendering/hwrenderer/scene/hw_decal.cpp +++ b/src/rendering/hwrenderer/scene/hw_decal.cpp @@ -59,21 +59,7 @@ void HWDecal::DrawDecal(HWDrawInfo *di, FRenderState &state) if (!di->isFullbrightScene()) DecalColor = DecalColor.Modulate(frontsector->SpecialColors[sector_t::sprites]); state.SetObjectColor(DecalColor); - state.SetLightIndex(dynlightindex); - - // add light probe contribution - if (di->Level->LightProbes.Size() > 0) - { - double x, y; - decal->GetXY(decal->Side, x, y); - LightProbe *probe = FindLightProbe(di->Level, x, y, decal->GetRealZ(decal->Side) * 0.5); - if (probe) - { - state.SetDynLight(probe->Red, probe->Green, probe->Blue); - } - } - state.SetTextureMode(decal->RenderStyle); state.SetRenderStyle(decal->RenderStyle); state.SetMaterial(texture, UF_Sprite, 0, CLAMP_XY, decal->Translation, -1); @@ -197,6 +183,11 @@ void HWWall::DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArraylinedef; @@ -342,7 +333,31 @@ void HWWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor dv[UL].u = dv[LL].u = lefttex / decalscale; dv[LR].u = dv[UR].u = righttex / decalscale; dv[LL].v = dv[LR].v = 1.f; - + + // lightmap texture index + for (i = 0; i < 4; i++) + { + dv[i].lindex = lindex; + } + + // lightmap texture coordinates + float tleft = left / linelength; + float tright = right / linelength; + float tuplft = ztop[0] != zbottom[0] ? (dv[UL].z - zbottom[0]) / (ztop[0] - zbottom[0]) : 0.0f; + float tuprgt = ztop[1] != zbottom[1] ? (dv[UR].z - zbottom[1]) / (ztop[1] - zbottom[1]) : 0.0f; + float tlolft = ztop[0] != zbottom[0] ? (dv[LL].z - zbottom[0]) / (ztop[0] - zbottom[0]) : 0.0f; + float tlorgt = ztop[1] != zbottom[1] ? (dv[LR].z - zbottom[1]) / (ztop[1] - zbottom[1]) : 0.0f; + + dv[LL].lu = mix(lightuv[LOLFT].u, lightuv[LORGT].u, tleft); + dv[LR].lu = mix(lightuv[LOLFT].u, lightuv[LORGT].u, tright); + dv[UL].lu = mix(lightuv[UPLFT].u, lightuv[UPRGT].u, tleft); + dv[UR].lu = mix(lightuv[UPLFT].u, lightuv[UPRGT].u, tright); + + dv[LL].lv = mix(lightuv[LOLFT].v, lightuv[UPLFT].v, tlolft); + dv[LR].lv = mix(lightuv[LORGT].v, lightuv[UPRGT].v, tlorgt); + dv[UL].lv = mix(lightuv[LOLFT].v, lightuv[UPLFT].v, tuplft); + dv[UR].lv = mix(lightuv[LORGT].v, lightuv[UPRGT].v, tuprgt); + // now clip to the top plane float vzt = (ztop[UL] - ztop[LL]) / linelength; float topleft = ztop[LL] + vzt * left; @@ -356,8 +371,12 @@ void HWWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor { // decal has to be clipped at the top // let texture clamping handle all extreme cases - dv[UL].v = (dv[UL].z - topleft) / (dv[UL].z - dv[LL].z)*dv[LL].v; - dv[UR].v = (dv[UR].z - topright) / (dv[UR].z - dv[LR].z)*dv[LR].v; + float t0 = (dv[UL].z - topleft) / (dv[UL].z - dv[LL].z); + float t1 = (dv[UR].z - topright) / (dv[UR].z - dv[LR].z); + dv[UL].v = t0 * dv[LL].v; + dv[UR].v = t1 * dv[LR].v; + dv[UL].lv = mix(dv[UL].lv, dv[LL].lv, t0); + dv[UR].lv = mix(dv[UR].lv, dv[LR].lv, t1); dv[UL].z = topleft; dv[UR].z = topright; } @@ -375,8 +394,12 @@ void HWWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor { // decal has to be clipped at the bottom // let texture clamping handle all extreme cases - dv[LL].v = (dv[UL].z - bottomleft) / (dv[UL].z - dv[LL].z)*(dv[LL].v - dv[UL].v) + dv[UL].v; - dv[LR].v = (dv[UR].z - bottomright) / (dv[UR].z - dv[LR].z)*(dv[LR].v - dv[UR].v) + dv[UR].v; + float t0 = (dv[UL].z - bottomleft) / (dv[UL].z - dv[LL].z); + float t1 = (dv[UR].z - bottomright) / (dv[UR].z - dv[LR].z); + dv[LL].v = t0 * (dv[LL].v - dv[UL].v) + dv[UL].v; + dv[LR].v = t1 * (dv[LR].v - dv[UR].v) + dv[UR].v; + dv[LL].lv = mix(dv[UL].lv, dv[LL].lv, t0); + dv[LR].lv = mix(dv[UR].lv, dv[LR].lv, t1); dv[LL].z = bottomleft; dv[LR].z = bottomright; } @@ -427,7 +450,7 @@ void HWWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &nor for (i = 0; i < 4; i++) { - verts.first[i].Set(dv[i].x, dv[i].z, dv[i].y, dv[i].u, dv[i].v); + verts.first[i].Set(dv[i].x, dv[i].z, dv[i].y, dv[i].u, dv[i].v, dv[i].lu, dv[i].lv, dv[i].lindex); } } diff --git a/src/rendering/hwrenderer/scene/hw_drawstructs.h b/src/rendering/hwrenderer/scene/hw_drawstructs.h index cf6c53027..6f4959f7b 100644 --- a/src/rendering/hwrenderer/scene/hw_drawstructs.h +++ b/src/rendering/hwrenderer/scene/hw_drawstructs.h @@ -401,6 +401,7 @@ struct DecalVertex { float x, y, z; float u, v; + float lu, lv, lindex; }; struct HWDecal @@ -433,7 +434,6 @@ inline float Dist2(float x1,float y1,float x2,float y2) bool hw_SetPlaneTextureRotation(const HWSectorPlane * secplane, FGameTexture * gltexture, VSMatrix &mat); void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata); -LightProbe* FindLightProbe(FLevelLocals* level, float x, float y, float z); extern const float LARGE_VALUE; diff --git a/src/rendering/hwrenderer/scene/hw_spritelight.cpp b/src/rendering/hwrenderer/scene/hw_spritelight.cpp index 64f98f650..73f5867da 100644 --- a/src/rendering/hwrenderer/scene/hw_spritelight.cpp +++ b/src/rendering/hwrenderer/scene/hw_spritelight.cpp @@ -46,61 +46,6 @@ T smoothstep(const T edge0, const T edge1, const T x) return t * t * (3.0 - 2.0 * t); } -LightProbe* FindLightProbe(FLevelLocals* level, float x, float y, float z) -{ - LightProbe* foundprobe = nullptr; - if (level->LightProbes.Size() > 0) - { -#if 1 - double rcpCellSize = 1.0 / level->LPCellSize; - int gridCenterX = (int)std::floor(x * rcpCellSize) - level->LPMinX; - int gridCenterY = (int)std::floor(y * rcpCellSize) - level->LPMinY; - int gridWidth = level->LPWidth; - int gridHeight = level->LPHeight; - float lastdist = 0.0f; - for (int gridY = gridCenterY - 1; gridY <= gridCenterY + 1; gridY++) - { - for (int gridX = gridCenterX - 1; gridX <= gridCenterX + 1; gridX++) - { - if (gridX >= 0 && gridY >= 0 && gridX < gridWidth && gridY < gridHeight) - { - const LightProbeCell& cell = level->LPCells[gridX + (size_t)gridY * gridWidth]; - for (int i = 0; i < cell.NumProbes; i++) - { - LightProbe* probe = cell.FirstProbe + i; - float dx = probe->X - x; - float dy = probe->Y - y; - float dz = probe->Z - z; - float dist = dx * dx + dy * dy + dz * dz; - if (!foundprobe || dist < lastdist) - { - foundprobe = probe; - lastdist = dist; - } - } - } - } - } -#else - float lastdist = 0.0f; - for (unsigned int i = 0; i < level->LightProbes.Size(); i++) - { - LightProbe *probe = &level->LightProbes[i]; - float dx = probe->X - x; - float dy = probe->Y - y; - float dz = probe->Z - z; - float dist = dx * dx + dy * dy + dz * dz; - if (i == 0 || dist < lastdist) - { - foundprobe = probe; - lastdist = dist; - } - } -#endif - } - return foundprobe; -} - static bool TraceLightVisbility(FLightNode* node, const FVector3& L, float dist) { FDynamicLight* light = node->lightsource; From 943cbd59e92510dd21a9029635f5f0e4f9c54688 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 25 Sep 2022 21:58:54 +0200 Subject: [PATCH 4/4] Add AABB tree collision structure to the doom level mesh for threadsafe trace calls for the sprite light --- src/CMakeLists.txt | 1 + .../hwrenderer/data/hw_collision.cpp | 819 ++++++++++++++++++ .../rendering/hwrenderer/data/hw_collision.h | 179 ++++ .../rendering/hwrenderer/data/hw_levelmesh.h | 9 + src/maploader/maploader.cpp | 4 +- src/rendering/hwrenderer/doom_levelmesh.cpp | 2 + .../hwrenderer/scene/hw_spritelight.cpp | 9 +- 7 files changed, 1018 insertions(+), 5 deletions(-) create mode 100644 src/common/rendering/hwrenderer/data/hw_collision.cpp create mode 100644 src/common/rendering/hwrenderer/data/hw_collision.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 824087af6..bda43dae9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1182,6 +1182,7 @@ set (PCH_SOURCES common/rendering/hwrenderer/data/hw_aabbtree.cpp common/rendering/hwrenderer/data/hw_shadowmap.cpp common/rendering/hwrenderer/data/hw_shaderpatcher.cpp + common/rendering/hwrenderer/data/hw_collision.cpp common/rendering/hwrenderer/postprocessing/hw_postprocessshader.cpp common/rendering/hwrenderer/postprocessing/hw_postprocess.cpp common/rendering/hwrenderer/postprocessing/hw_postprocess_cvars.cpp diff --git a/src/common/rendering/hwrenderer/data/hw_collision.cpp b/src/common/rendering/hwrenderer/data/hw_collision.cpp new file mode 100644 index 000000000..e83afbef6 --- /dev/null +++ b/src/common/rendering/hwrenderer/data/hw_collision.cpp @@ -0,0 +1,819 @@ +/* +** Level mesh collision detection +** Copyright (c) 2018 Magnus Norddahl +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#include "hw_collision.h" +#include +#include +#include +#ifndef NO_SSE +#include +#endif + +TriangleMeshShape::TriangleMeshShape(const FVector3 *vertices, int num_vertices, const unsigned int *elements, int num_elements) + : vertices(vertices), num_vertices(num_vertices), elements(elements), num_elements(num_elements) +{ + int num_triangles = num_elements / 3; + if (num_triangles <= 0) + return; + + std::vector triangles; + std::vector centroids; + triangles.reserve(num_triangles); + centroids.reserve(num_triangles); + for (int i = 0; i < num_triangles; i++) + { + triangles.push_back(i); + + int element_index = i * 3; + FVector3 centroid = (vertices[elements[element_index + 0]] + vertices[elements[element_index + 1]] + vertices[elements[element_index + 2]]) * (1.0f / 3.0f); + centroids.push_back(centroid); + } + + std::vector work_buffer(num_triangles * 2); + + root = subdivide(&triangles[0], (int)triangles.size(), ¢roids[0], &work_buffer[0]); +} + +float TriangleMeshShape::sweep(TriangleMeshShape *shape1, SphereShape *shape2, const FVector3 &target) +{ + return sweep(shape1, shape2, shape1->root, target); +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape1, TriangleMeshShape *shape2) +{ + return find_any_hit(shape1, shape2, shape1->root, shape2->root); +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape1, SphereShape *shape2) +{ + return find_any_hit(shape1, shape2, shape1->root); +} + +std::vector TriangleMeshShape::find_all_hits(TriangleMeshShape* shape1, SphereShape* shape2) +{ + std::vector hits; + find_all_hits(shape1, shape2, shape1->root, hits); + return hits; +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape, const FVector3 &ray_start, const FVector3 &ray_end) +{ + return find_any_hit(shape, RayBBox(ray_start, ray_end), shape->root); +} + +TraceHit TriangleMeshShape::find_first_hit(TriangleMeshShape *shape, const FVector3 &ray_start, const FVector3 &ray_end) +{ + TraceHit hit; + + // Perform segmented tracing to keep the ray AABB box smaller + + FVector3 ray_dir = ray_end - ray_start; + float tracedist = (float)ray_dir.Length(); + float segmentlen = std::max(100.0f, tracedist / 20.0f); + for (float t = 0.0f; t < tracedist; t += segmentlen) + { + float segstart = t / tracedist; + float segend = std::min(t + segmentlen, tracedist) / tracedist; + + find_first_hit(shape, RayBBox(ray_start + ray_dir * segstart, ray_start + ray_dir * segend), shape->root, &hit); + if (hit.fraction < 1.0f) + { + hit.fraction = segstart * (1.0f - hit.fraction) + segend * hit.fraction; + break; + } + } + + return hit; +} + +float TriangleMeshShape::sweep(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target) +{ + if (sweep_overlap_bv_sphere(shape1, shape2, a, target)) + { + if (shape1->is_leaf(a)) + { + return sweep_intersect_triangle_sphere(shape1, shape2, a, target); + } + else + { + return std::min(sweep(shape1, shape2, shape1->nodes[a].left, target), sweep(shape1, shape2, shape1->nodes[a].right, target)); + } + } + return 1.0f; +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape1, SphereShape *shape2, int a) +{ + if (overlap_bv_sphere(shape1, shape2, a)) + { + if (shape1->is_leaf(a)) + { + return overlap_triangle_sphere(shape1, shape2, a); + } + else + { + if (find_any_hit(shape1, shape2, shape1->nodes[a].left)) + return true; + else + return find_any_hit(shape1, shape2, shape1->nodes[a].right); + } + } + return false; +} + +void TriangleMeshShape::find_all_hits(TriangleMeshShape* shape1, SphereShape* shape2, int a, std::vector& hits) +{ + if (overlap_bv_sphere(shape1, shape2, a)) + { + if (shape1->is_leaf(a)) + { + if (overlap_triangle_sphere(shape1, shape2, a)) + { + hits.push_back(shape1->nodes[a].element_index / 3); + } + } + else + { + find_all_hits(shape1, shape2, shape1->nodes[a].left, hits); + find_all_hits(shape1, shape2, shape1->nodes[a].right, hits); + } + } +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b) +{ + bool leaf_a = shape1->is_leaf(a); + bool leaf_b = shape2->is_leaf(b); + if (leaf_a && leaf_b) + { + return overlap_triangle_triangle(shape1, shape2, a, b); + } + else if (!leaf_a && !leaf_b) + { + if (overlap_bv(shape1, shape2, a, b)) + { + if (shape1->volume(a) > shape2->volume(b)) + { + if (find_any_hit(shape1, shape2, shape1->nodes[a].left, b)) + return true; + else + return find_any_hit(shape1, shape2, shape1->nodes[a].right, b); + } + else + { + if (find_any_hit(shape1, shape2, a, shape2->nodes[b].left)) + return true; + else + return find_any_hit(shape1, shape2, a, shape2->nodes[b].right); + } + } + return false; + } + else if (leaf_a) + { + if (overlap_bv_triangle(shape2, shape1, b, a)) + { + if (find_any_hit(shape1, shape2, a, shape2->nodes[b].left)) + return true; + else + return find_any_hit(shape1, shape2, a, shape2->nodes[b].right); + } + return false; + } + else + { + if (overlap_bv_triangle(shape1, shape2, a, b)) + { + if (find_any_hit(shape1, shape2, shape1->nodes[a].left, b)) + return true; + else + return find_any_hit(shape1, shape2, shape1->nodes[a].right, b); + } + return false; + } +} + +bool TriangleMeshShape::find_any_hit(TriangleMeshShape *shape, const RayBBox &ray, int a) +{ + if (overlap_bv_ray(shape, ray, a)) + { + if (shape->is_leaf(a)) + { + float baryB, baryC; + return intersect_triangle_ray(shape, ray, a, baryB, baryC) < 1.0f; + } + else + { + if (find_any_hit(shape, ray, shape->nodes[a].left)) + return true; + else + return find_any_hit(shape, ray, shape->nodes[a].right); + } + } + return false; +} + +void TriangleMeshShape::find_first_hit(TriangleMeshShape *shape, const RayBBox &ray, int a, TraceHit *hit) +{ + if (overlap_bv_ray(shape, ray, a)) + { + if (shape->is_leaf(a)) + { + float baryB, baryC; + float t = intersect_triangle_ray(shape, ray, a, baryB, baryC); + if (t < hit->fraction) + { + hit->fraction = t; + hit->triangle = shape->nodes[a].element_index / 3; + hit->b = baryB; + hit->c = baryC; + } + } + else + { + find_first_hit(shape, ray, shape->nodes[a].left, hit); + find_first_hit(shape, ray, shape->nodes[a].right, hit); + } + } +} + +bool TriangleMeshShape::overlap_bv_ray(TriangleMeshShape *shape, const RayBBox &ray, int a) +{ + return IntersectionTest::ray_aabb(ray, shape->nodes[a].aabb) == IntersectionTest::overlap; +} + +float TriangleMeshShape::intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a, float &barycentricB, float &barycentricC) +{ + const int start_element = shape->nodes[a].element_index; + + FVector3 p[3] = + { + shape->vertices[shape->elements[start_element]], + shape->vertices[shape->elements[start_element + 1]], + shape->vertices[shape->elements[start_element + 2]] + }; + + // Moeller–Trumbore ray-triangle intersection algorithm: + + FVector3 D = ray.end - ray.start; + + // Find vectors for two edges sharing p[0] + FVector3 e1 = p[1] - p[0]; + FVector3 e2 = p[2] - p[0]; + + // Begin calculating determinant - also used to calculate u parameter + FVector3 P = D ^ e2; // cross(D, e2); + float det = e1 | P; // dot(e1, P); + + // Backface check + //if (det < 0.0f) + // return 1.0f; + + // If determinant is near zero, ray lies in plane of triangle + if (det > -FLT_EPSILON && det < FLT_EPSILON) + return 1.0f; + + float inv_det = 1.0f / det; + + // Calculate distance from p[0] to ray origin + FVector3 T = ray.start - p[0]; + + // Calculate u parameter and test bound + float u = (T | P) * inv_det; // dot(T, P) * inv_det; + + // Check if the intersection lies outside of the triangle + if (u < 0.f || u > 1.f) + return 1.0f; + + // Prepare to test v parameter + FVector3 Q = T ^ e1; // cross(T, e1); + + // Calculate V parameter and test bound + float v = (D | Q) * inv_det; // dot(D, Q) * inv_det; + + // The intersection lies outside of the triangle + if (v < 0.f || u + v > 1.f) + return 1.0f; + + float t = (e2 | Q) * inv_det; //dot(e2, Q) * inv_det; + if (t <= FLT_EPSILON) + return 1.0f; + + // Return hit location on triangle in barycentric coordinates + barycentricB = u; + barycentricC = v; + + return t; +} + +bool TriangleMeshShape::sweep_overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target) +{ + // Convert to ray test by expanding the AABB: + + CollisionBBox aabb = shape1->nodes[a].aabb; + aabb.Extents.X += shape2->radius; + aabb.Extents.Y += shape2->radius; + aabb.Extents.Z += shape2->radius; + + return IntersectionTest::ray_aabb(RayBBox(shape2->center, target), aabb) == IntersectionTest::overlap; +} + +float TriangleMeshShape::sweep_intersect_triangle_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target) +{ + const int start_element = shape1->nodes[a].element_index; + + FVector3 p[3] = + { + shape1->vertices[shape1->elements[start_element]], + shape1->vertices[shape1->elements[start_element + 1]], + shape1->vertices[shape1->elements[start_element + 2]] + }; + + FVector3 c = shape2->center; + FVector3 e = target; + float r = shape2->radius; + + // Dynamic intersection test between a ray and the minkowski sum of the sphere and polygon: + + FVector3 n = ((p[1] - p[0]) ^ (p[2] - p[0])).Unit(); // normalize(cross(p[1] - p[0], p[2] - p[0])); + FVector4 plane(n, -(n | p[0])); // plane(n, -dot(n, p[0])); + + // Step 1: Plane intersect test + + float sc = (plane | FVector4(c, 1.0f)); // dot(plane, FVector4(c, 1.0f)); + float se = (plane | FVector4(e, 1.0f)); // dot(plane, FVector4(e, 1.0f)); + bool same_side = sc * se > 0.0f; + + if (same_side && std::abs(sc) > r && std::abs(se) > r) + return 1.0f; + + // Step 1a: Check if point is in polygon (using crossing ray test in 2d) + { + float t = (sc - r) / (sc - se); + + FVector3 vt = c + (e - c) * t; + + FVector3 u0 = p[1] - p[0]; + FVector3 u1 = p[2] - p[0]; + + FVector2 v_2d[3] = + { + FVector2(0.0f, 0.0f), + FVector2((u0 | u0), 0.0f), // FVector2(dot(u0, u0), 0.0f), + FVector2(0.0f, (u1 | u1)) // FVector2(0.0f, dot(u1, u1)) + }; + + FVector2 point((u0 | vt), (u1 | vt)); // point(dot(u0, vt), dot(u1, vt)); + + bool inside = false; + FVector2 e0 = v_2d[2]; + bool y0 = e0.Y >= point.Y; + for (int i = 0; i < 3; i++) + { + FVector2 e1 = v_2d[i]; + bool y1 = e1.Y >= point.Y; + + if (y0 != y1 && ((e1.Y - point.Y) * (e0.X - e1.X) >= (e1.X - point.X) * (e0.Y - e1.Y)) == y1) + inside = !inside; + + y0 = y1; + e0 = e1; + } + + if (inside) + return t; + } + + // Step 2: Edge intersect test + + FVector3 ke[3] = + { + p[1] - p[0], + p[2] - p[1], + p[0] - p[2], + }; + + FVector3 kg[3] = + { + p[0] - c, + p[1] - c, + p[2] - c, + }; + + FVector3 ks = e - c; + + float kgg[3]; + float kgs[3]; + float kss[3]; + + for (int i = 0; i < 3; i++) + { + float kee = (ke[i] | ke[i]); // dot(ke[i], ke[i]); + float keg = (ke[i] | kg[i]); // dot(ke[i], kg[i]); + float kes = (ke[i] | ks); // dot(ke[i], ks); + kgg[i] = (kg[i] | kg[i]); // dot(kg[i], kg[i]); + kgs[i] = (kg[i] | ks); // dot(kg[i], ks); + kss[i] = (ks | ks); // dot(ks, ks); + + float aa = kee * kss[i] - kes * kes; + float bb = 2 * (keg * kes - kee * kgs[i]); + float cc = kee * (kgg[i] - r * r) - keg * keg; + + float sign = (bb >= 0.0f) ? 1.0f : -1.0f; + float q = -0.5f * (bb + sign * std::sqrt(bb * bb - 4 * aa * cc)); + float t0 = q / aa; + float t1 = cc / q; + + float t; + if (t0 < 0.0f || t0 > 1.0f) + t = t1; + else if (t1 < 0.0f || t1 > 1.0f) + t = t0; + else + t = std::min(t0, t1); + + if (t >= 0.0f && t <= 1.0f) + { + FVector3 ct = c + ks * t; + float d = ((ct - p[i]) | ke[i]); // dot(ct - p[i], ke[i]); + if (d >= 0.0f && d <= kee) + return t; + } + } + + // Step 3: Point intersect test + + for (int i = 0; i < 3; i++) + { + float aa = kss[i]; + float bb = -2.0f * kgs[i]; + float cc = kgg[i] - r * r; + + float sign = (bb >= 0.0f) ? 1.0f : -1.0f; + float q = -0.5f * (bb + sign * std::sqrt(bb * bb - 4 * aa * cc)); + float t0 = q / aa; + float t1 = cc / q; + + float t; + if (t0 < 0.0f || t0 > 1.0f) + t = t1; + else if (t1 < 0.0f || t1 > 1.0f) + t = t0; + else + t = std::min(t0, t1); + + if (t >= 0.0f && t <= 1.0f) + return t; + } + + return 1.0f; +} + +bool TriangleMeshShape::overlap_bv(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b) +{ + return IntersectionTest::aabb(shape1->nodes[a].aabb, shape2->nodes[b].aabb) == IntersectionTest::overlap; +} + +bool TriangleMeshShape::overlap_bv_triangle(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b) +{ + return false; +} + +bool TriangleMeshShape::overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a) +{ + return IntersectionTest::sphere_aabb(shape2->center, shape2->radius, shape1->nodes[a].aabb) == IntersectionTest::overlap; +} + +bool TriangleMeshShape::overlap_triangle_triangle(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b) +{ + return false; +} + +bool TriangleMeshShape::overlap_triangle_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int shape1_node_index) +{ + // http://realtimecollisiondetection.net/blog/?p=103 + + int element_index = shape1->nodes[shape1_node_index].element_index; + + FVector3 P = shape2->center; + FVector3 A = shape1->vertices[shape1->elements[element_index]] - P; + FVector3 B = shape1->vertices[shape1->elements[element_index + 1]] - P; + FVector3 C = shape1->vertices[shape1->elements[element_index + 2]] - P; + float r = shape2->radius; + float rr = r * r; + + // Testing if sphere lies outside the triangle plane + FVector3 V = ((B - A) ^ (C - A)); // cross(B - A, C - A); + float d = A | V; // dot(A, V); + float e = V | V; // dot(V, V); + bool sep1 = d * d > rr * e; + + // Testing if sphere lies outside a triangle vertex + float aa = A | A; // dot(A, A); + float ab = A | B; // dot(A, B); + float ac = A | C; // dot(A, C); + float bb = B | B; // dot(B, B); + float bc = B | C; // dot(B, C); + float cc = C | C; // dot(C, C); + bool sep2 = (aa > rr) && (ab > aa) && (ac > aa); + bool sep3 = (bb > rr) && (ab > bb) && (bc > bb); + bool sep4 = (cc > rr) && (ac > cc) && (bc > cc); + + // Testing if sphere lies outside a triangle edge + FVector3 AB = B - A; + FVector3 BC = C - B; + FVector3 CA = A - C; + float d1 = ab - aa; + float d2 = bc - bb; + float d3 = ac - cc; + float e1 = (AB | AB); // dot(AB, AB) + float e2 = (BC | BC); // dot(BC, BC) + float e3 = (CA | CA); // dot(CA, CA) + FVector3 Q1 = A * e1 - AB * d1; + FVector3 Q2 = B * e2 - BC * d2; + FVector3 Q3 = C * e3 - CA * d3; + FVector3 QC = C * e1 - Q1; + FVector3 QA = A * e2 - Q2; + FVector3 QB = B * e3 - Q3; + bool sep5 = ((Q1 | Q1) > rr * e1 * e1) && ((Q1 | QC) > 0.0f); // (dot(Q1, Q1) > rr * e1 * e1) && (dot(Q1, QC) > 0.0f); + bool sep6 = ((Q2 | Q2) > rr * e2 * e2) && ((Q2 | QA) > 0.0f); // (dot(Q2, Q2) > rr * e2 * e2) && (dot(Q2, QA) > 0.0f); + bool sep7 = ((Q3 | Q3) > rr * e3 * e3) && ((Q3 | QB) > 0.0f); // (dot(Q3, Q3) > rr * e3 * e3) && (dot(Q3, QB) > 0.0f); + + bool separated = sep1 || sep2 || sep3 || sep4 || sep5 || sep6 || sep7; + return (!separated); +} + +bool TriangleMeshShape::is_leaf(int node_index) +{ + return nodes[node_index].element_index != -1; +} + +float TriangleMeshShape::volume(int node_index) +{ + const FVector3 &extents = nodes[node_index].aabb.Extents; + return extents.X * extents.Y * extents.Z; +} + +int TriangleMeshShape::get_min_depth() const +{ + std::function visit; + visit = [&](int level, int node_index) -> int { + const Node &node = nodes[node_index]; + if (node.element_index == -1) + return std::min(visit(level + 1, node.left), visit(level + 1, node.right)); + else + return level; + }; + return visit(1, root); +} + +int TriangleMeshShape::get_max_depth() const +{ + std::function visit; + visit = [&](int level, int node_index) -> int { + const Node &node = nodes[node_index]; + if (node.element_index == -1) + return std::max(visit(level + 1, node.left), visit(level + 1, node.right)); + else + return level; + }; + return visit(1, root); +} + +float TriangleMeshShape::get_average_depth() const +{ + std::function visit; + visit = [&](int level, int node_index) -> float { + const Node &node = nodes[node_index]; + if (node.element_index == -1) + return visit(level + 1, node.left) + visit(level + 1, node.right); + else + return (float)level; + }; + float depth_sum = visit(1, root); + int leaf_count = (num_elements / 3); + return depth_sum / leaf_count; +} + +float TriangleMeshShape::get_balanced_depth() const +{ + return std::log2((float)(num_elements / 3)); +} + +int TriangleMeshShape::subdivide(int *triangles, int num_triangles, const FVector3 *centroids, int *work_buffer) +{ + if (num_triangles == 0) + return -1; + + // Find bounding box and median of the triangle centroids + FVector3 median; + FVector3 min, max; + min = vertices[elements[triangles[0] * 3]]; + max = min; + for (int i = 0; i < num_triangles; i++) + { + int element_index = triangles[i] * 3; + for (int j = 0; j < 3; j++) + { + const FVector3 &vertex = vertices[elements[element_index + j]]; + + min.X = std::min(min.X, vertex.X); + min.Y = std::min(min.Y, vertex.Y); + min.Z = std::min(min.Z, vertex.Z); + + max.X = std::max(max.X, vertex.X); + max.Y = std::max(max.Y, vertex.Y); + max.Z = std::max(max.Z, vertex.Z); + } + + median += centroids[triangles[i]]; + } + median /= (float)num_triangles; + + if (num_triangles == 1) // Leaf node + { + nodes.push_back(Node(min, max, triangles[0] * 3)); + return (int)nodes.size() - 1; + } + + // Find the longest axis + float axis_lengths[3] = + { + max.X - min.X, + max.Y - min.Y, + max.Z - min.Z + }; + + int axis_order[3] = { 0, 1, 2 }; + std::sort(axis_order, axis_order + 3, [&](int a, int b) { return axis_lengths[a] > axis_lengths[b]; }); + + // Try split at longest axis, then if that fails the next longest, and then the remaining one + int left_count, right_count; + FVector3 axis; + for (int attempt = 0; attempt < 3; attempt++) + { + // Find the split plane for axis + switch (axis_order[attempt]) + { + default: + case 0: axis = FVector3(1.0f, 0.0f, 0.0f); break; + case 1: axis = FVector3(0.0f, 1.0f, 0.0f); break; + case 2: axis = FVector3(0.0f, 0.0f, 1.0f); break; + } + FVector4 plane(axis, -(median | axis)); // plane(axis, -dot(median, axis)); + + // Split triangles into two + left_count = 0; + right_count = 0; + for (int i = 0; i < num_triangles; i++) + { + int triangle = triangles[i]; + int element_index = triangle * 3; + + float side = (FVector4(centroids[triangles[i]], 1.0f) | plane); // dot(FVector4(centroids[triangles[i]], 1.0f), plane); + if (side >= 0.0f) + { + work_buffer[left_count] = triangle; + left_count++; + } + else + { + work_buffer[num_triangles + right_count] = triangle; + right_count++; + } + } + + if (left_count != 0 && right_count != 0) + break; + } + + // Check if something went wrong when splitting and do a random split instead + if (left_count == 0 || right_count == 0) + { + left_count = num_triangles / 2; + right_count = num_triangles - left_count; + } + else + { + // Move result back into triangles list: + for (int i = 0; i < left_count; i++) + triangles[i] = work_buffer[i]; + for (int i = 0; i < right_count; i++) + triangles[i + left_count] = work_buffer[num_triangles + i]; + } + + // Create child nodes: + int left_index = -1; + int right_index = -1; + if (left_count > 0) + left_index = subdivide(triangles, left_count, centroids, work_buffer); + if (right_count > 0) + right_index = subdivide(triangles + left_count, right_count, centroids, work_buffer); + + nodes.push_back(Node(min, max, left_index, right_index)); + return (int)nodes.size() - 1; +} + +///////////////////////////////////////////////////////////////////////////// + +IntersectionTest::OverlapResult IntersectionTest::sphere_aabb(const FVector3 ¢er, float radius, const CollisionBBox &aabb) +{ + FVector3 a = aabb.min - center; + FVector3 b = center - aabb.max; + a.X = std::max(a.X, 0.0f); + a.Y = std::max(a.Y, 0.0f); + a.Z = std::max(a.Z, 0.0f); + b.X = std::max(b.X, 0.0f); + b.Y = std::max(b.Y, 0.0f); + b.Z = std::max(b.Z, 0.0f); + FVector3 e = a + b; + float d = (e | e); // dot(e, e); + if (d > radius * radius) + return disjoint; + else + return overlap; +} + +IntersectionTest::OverlapResult IntersectionTest::aabb(const CollisionBBox& a, const CollisionBBox& b) +{ + if (a.min.X > b.max.X || b.min.X > a.max.X || + a.min.Y > b.max.Y || b.min.Y > a.max.Y || + a.min.Z > b.max.Z || b.min.Z > a.max.Z) + { + return disjoint; + } + else + { + return overlap; + } +} + +static const uint32_t clearsignbitmask[] = { 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff }; + +IntersectionTest::OverlapResult IntersectionTest::ray_aabb(const RayBBox &ray, const CollisionBBox &aabb) +{ +#ifndef NO_SSE + + __m128 v = _mm_loadu_ps(&ray.v.X); + __m128 w = _mm_loadu_ps(&ray.w.X); + __m128 h = _mm_loadu_ps(&aabb.Extents.X); + __m128 c = _mm_sub_ps(_mm_loadu_ps(&ray.c.X), _mm_loadu_ps(&aabb.Center.X)); + + __m128 clearsignbit = _mm_loadu_ps(reinterpret_cast(clearsignbitmask)); + + __m128 abs_c = _mm_and_ps(c, clearsignbit); + int mask = _mm_movemask_ps(_mm_cmpgt_ps(abs_c, _mm_add_ps(v, h))); + if (mask & 7) + return disjoint; + + __m128 c1 = _mm_shuffle_ps(c, c, _MM_SHUFFLE(3, 0, 0, 1)); // c.Y, c.X, c.X + __m128 c2 = _mm_shuffle_ps(c, c, _MM_SHUFFLE(3, 1, 2, 2)); // c.Z, c.Z, c.Y + __m128 w1 = _mm_shuffle_ps(w, w, _MM_SHUFFLE(3, 1, 2, 2)); // w.Z, w.Z, w.Y + __m128 w2 = _mm_shuffle_ps(w, w, _MM_SHUFFLE(3, 0, 0, 1)); // w.Y, w.X, w.X + __m128 lhs = _mm_and_ps(_mm_sub_ps(_mm_mul_ps(c1, w1), _mm_mul_ps(c2, w2)), clearsignbit); + + __m128 h1 = _mm_shuffle_ps(h, h, _MM_SHUFFLE(3, 0, 0, 1)); // h.Y, h.X, h.X + __m128 h2 = _mm_shuffle_ps(h, h, _MM_SHUFFLE(3, 1, 2, 2)); // h.Z, h.Z, h.Y + __m128 v1 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 1, 2, 2)); // v.Z, v.Z, v.Y + __m128 v2 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 0, 0, 1)); // v.Y, v.X, v.X + __m128 rhs = _mm_add_ps(_mm_mul_ps(h1, v1), _mm_mul_ps(h2, v2)); + + mask = _mm_movemask_ps(_mm_cmpgt_ps(lhs, rhs)); + return (mask & 7) ? disjoint : overlap; + +#else + const FVector3 &v = ray.v; + const FVector3 &w = ray.w; + const FVector3 &h = aabb.Extents; + auto c = ray.c - aabb.Center; + + if (std::abs(c.X) > v.X + h.X || std::abs(c.Y) > v.Y + h.Y || std::abs(c.Z) > v.Z + h.Z) + return disjoint; + + if (std::abs(c.Y * w.Z - c.Z * w.Y) > h.Y * v.Z + h.Z * v.Y || + std::abs(c.X * w.Z - c.Z * w.X) > h.X * v.Z + h.Z * v.X || + std::abs(c.X * w.Y - c.Y * w.X) > h.X * v.Y + h.Y * v.X) + return disjoint; + + return overlap; +#endif +} diff --git a/src/common/rendering/hwrenderer/data/hw_collision.h b/src/common/rendering/hwrenderer/data/hw_collision.h new file mode 100644 index 000000000..756ffc031 --- /dev/null +++ b/src/common/rendering/hwrenderer/data/hw_collision.h @@ -0,0 +1,179 @@ +/* +** Level mesh collision detection +** Copyright (c) 2018 Magnus Norddahl +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#pragma once + +#include "common/utility/vectors.h" +#include +#include + +class SphereShape +{ +public: + SphereShape() { } + SphereShape(const FVector3 ¢er, float radius) : center(center), radius(radius) { } + + FVector3 center; + float radius = 0.0f; +}; + +struct TraceHit +{ + float fraction = 1.0f; + int triangle = -1; + float b = 0.0f; + float c = 0.0f; +}; + +class CollisionBBox +{ +public: + CollisionBBox() = default; + + CollisionBBox(const FVector3 &aabb_min, const FVector3 &aabb_max) + { + min = aabb_min; + max = aabb_max; + auto halfmin = aabb_min * 0.5f; + auto halfmax = aabb_max * 0.5f; + Center = halfmax + halfmin; + Extents = halfmax - halfmin; + } + + FVector3 min; + FVector3 max; + FVector3 Center; + FVector3 Extents; + float ssePadding = 0.0f; // Needed to safely load Extents directly into a sse register +}; + +class RayBBox +{ +public: + RayBBox(const FVector3 &ray_start, const FVector3 &ray_end) : start(ray_start), end(ray_end) + { + c = (ray_start + ray_end) * 0.5f; + w = ray_end - c; + v.X = std::abs(w.X); + v.Y = std::abs(w.Y); + v.Z = std::abs(w.Z); + } + + FVector3 start, end; + FVector3 c, w, v; + float ssePadding = 0.0f; // Needed to safely load v directly into a sse register +}; + +class TriangleMeshShape +{ +public: + TriangleMeshShape(const FVector3 *vertices, int num_vertices, const unsigned int *elements, int num_elements); + + int get_min_depth() const; + int get_max_depth() const; + float get_average_depth() const; + float get_balanced_depth() const; + + const CollisionBBox &get_bbox() const { return nodes[root].aabb; } + + static float sweep(TriangleMeshShape *shape1, SphereShape *shape2, const FVector3 &target); + + static bool find_any_hit(TriangleMeshShape *shape1, TriangleMeshShape *shape2); + static bool find_any_hit(TriangleMeshShape *shape1, SphereShape *shape2); + static bool find_any_hit(TriangleMeshShape *shape, const FVector3 &ray_start, const FVector3 &ray_end); + + static std::vector find_all_hits(TriangleMeshShape* shape1, SphereShape* shape2); + + static TraceHit find_first_hit(TriangleMeshShape *shape, const FVector3 &ray_start, const FVector3 &ray_end); + + struct Node + { + Node() = default; + Node(const FVector3 &aabb_min, const FVector3 &aabb_max, int element_index) : aabb(aabb_min, aabb_max), element_index(element_index) { } + Node(const FVector3 &aabb_min, const FVector3 &aabb_max, int left, int right) : aabb(aabb_min, aabb_max), left(left), right(right) { } + + CollisionBBox aabb; + int left = -1; + int right = -1; + int element_index = -1; + }; + + const std::vector& get_nodes() const { return nodes; } + int get_root() const { return root; } + +private: + const FVector3 *vertices = nullptr; + const int num_vertices = 0; + const unsigned int *elements = nullptr; + int num_elements = 0; + + std::vector nodes; + int root = -1; + + static float sweep(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target); + + static bool find_any_hit(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b); + static bool find_any_hit(TriangleMeshShape *shape1, SphereShape *shape2, int a); + static bool find_any_hit(TriangleMeshShape *shape1, const RayBBox &ray, int a); + + static void find_all_hits(TriangleMeshShape* shape1, SphereShape* shape2, int a, std::vector& hits); + + static void find_first_hit(TriangleMeshShape *shape1, const RayBBox &ray, int a, TraceHit *hit); + + inline static bool overlap_bv_ray(TriangleMeshShape *shape, const RayBBox &ray, int a); + inline static float intersect_triangle_ray(TriangleMeshShape *shape, const RayBBox &ray, int a, float &barycentricB, float &barycentricC); + + inline static bool sweep_overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target); + inline static float sweep_intersect_triangle_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a, const FVector3 &target); + + inline static bool overlap_bv(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b); + inline static bool overlap_bv_triangle(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b); + inline static bool overlap_bv_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a); + inline static bool overlap_triangle_triangle(TriangleMeshShape *shape1, TriangleMeshShape *shape2, int a, int b); + inline static bool overlap_triangle_sphere(TriangleMeshShape *shape1, SphereShape *shape2, int a); + + inline bool is_leaf(int node_index); + inline float volume(int node_index); + + int subdivide(int *triangles, int num_triangles, const FVector3 *centroids, int *work_buffer); +}; + +class IntersectionTest +{ +public: + enum Result + { + outside, + inside, + intersecting, + }; + + enum OverlapResult + { + disjoint, + overlap + }; + + static OverlapResult sphere_aabb(const FVector3 ¢er, float radius, const CollisionBBox &aabb); + static OverlapResult aabb(const CollisionBBox &a, const CollisionBBox &b); + static OverlapResult ray_aabb(const RayBBox &ray, const CollisionBBox &box); +}; diff --git a/src/common/rendering/hwrenderer/data/hw_levelmesh.h b/src/common/rendering/hwrenderer/data/hw_levelmesh.h index c385f8738..8a48c99f3 100644 --- a/src/common/rendering/hwrenderer/data/hw_levelmesh.h +++ b/src/common/rendering/hwrenderer/data/hw_levelmesh.h @@ -3,6 +3,7 @@ #include "tarray.h" #include "vectors.h" +#include "hw_collision.h" namespace hwrenderer { @@ -16,6 +17,14 @@ public: TArray MeshUVIndex; TArray MeshElements; TArray MeshSurfaces; + + std::unique_ptr Collision; + + bool Trace(const FVector3& start, FVector3 direction, float maxDist) + { + FVector3 end = start + direction * std::max(maxDist - 10.0f, 0.0f); + return !TriangleMeshShape::find_any_hit(Collision.get(), start, end); + } }; } // namespace diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 65ad0e2c0..b12a018a3 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -3336,8 +3336,8 @@ void MapLoader::LoadLightmap(MapData *map) Level->LMTextureCount = 0; Level->LMTextureSize = 0; - if (!Args->CheckParm("-enablelightmaps")) - return; // this feature is still too early WIP to allow general access + //if (!Args->CheckParm("-enablelightmaps")) + // return; // this feature is still too early WIP to allow general access if (!map->Size(ML_LIGHTMAP)) return; diff --git a/src/rendering/hwrenderer/doom_levelmesh.cpp b/src/rendering/hwrenderer/doom_levelmesh.cpp index ea8aa314d..bbc4c6bb7 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.cpp +++ b/src/rendering/hwrenderer/doom_levelmesh.cpp @@ -56,6 +56,8 @@ DoomLevelMesh::DoomLevelMesh(FLevelLocals &doomMap) } } } + + Collision = std::make_unique(MeshVertices.Data(), MeshVertices.Size(), MeshElements.Data(), MeshElements.Size()); } void DoomLevelMesh::CreateSideSurfaces(FLevelLocals &doomMap, side_t *side) diff --git a/src/rendering/hwrenderer/scene/hw_spritelight.cpp b/src/rendering/hwrenderer/scene/hw_spritelight.cpp index 73f5867da..84b6d5c3d 100644 --- a/src/rendering/hwrenderer/scene/hw_spritelight.cpp +++ b/src/rendering/hwrenderer/scene/hw_spritelight.cpp @@ -49,11 +49,14 @@ T smoothstep(const T edge0, const T edge1, const T x) static bool TraceLightVisbility(FLightNode* node, const FVector3& L, float dist) { FDynamicLight* light = node->lightsource; - if (!light->Trace()) + if (!light->Trace() || !level.levelMesh) return true; - FTraceResults results; - return !Trace(light->Pos, light->Sector, DVector3(-L.X, -L.Y, -L.Z), dist, 0, ML_BLOCKING, nullptr, results); + // Note: this is not thread safe (modifies validcount and calls other setup functions) + // FTraceResults results; + // return !Trace(light->Pos, light->Sector, DVector3(-L.X, -L.Y, -L.Z), dist, 0, ML_BLOCKING, nullptr, results); + + return level.levelMesh->Trace(FVector3((float)light->Pos.X, (float)light->Pos.Y, (float)light->Pos.Z), FVector3(-L.X, -L.Y, -L.Z), dist); } //==========================================================================