diff --git a/src/hwrenderer/scene/hw_spritelight.cpp b/src/hwrenderer/scene/hw_spritelight.cpp index c44a3179a7..6b55f67cea 100644 --- a/src/hwrenderer/scene/hw_spritelight.cpp +++ b/src/hwrenderer/scene/hw_spritelight.cpp @@ -34,6 +34,7 @@ #include "hwrenderer/dynlights/hw_dynlightdata.h" #include "hwrenderer/dynlights/hw_shadowmap.h" #include "hwrenderer/scene/hw_drawinfo.h" +#include "r_data/models/models.h" template T smoothstep(const T edge0, const T edge1, const T x) @@ -138,49 +139,6 @@ void HWDrawInfo::GetDynSpriteLight(AActor *thing, particle_t *particle, float *o } } -// Check if circle potentially intersects with node AABB -static bool CheckBBoxCircle(float *bbox, float x, float y, float radiusSquared) -{ - float centerX = (bbox[BOXRIGHT] + bbox[BOXLEFT]) * 0.5f; - float centerY = (bbox[BOXBOTTOM] + bbox[BOXTOP]) * 0.5f; - float extentX = (bbox[BOXRIGHT] - bbox[BOXLEFT]) * 0.5f; - float extentY = (bbox[BOXBOTTOM] - bbox[BOXTOP]) * 0.5f; - float aabbRadiusSquared = extentX * extentX + extentY * extentY; - x -= centerX; - y -= centerY; - float dist = x * x + y * y; - return dist <= radiusSquared + aabbRadiusSquared; -} - -template -void BSPNodeWalkCircle(void *node, float x, float y, float radiusSquared, const Callback &callback) -{ - while (!((size_t)node & 1)) - { - node_t *bsp = (node_t *)node; - - if (CheckBBoxCircle(bsp->bbox[0], x, y, radiusSquared)) - BSPNodeWalkCircle(bsp->children[0], x, y, radiusSquared, callback); - - if (!CheckBBoxCircle(bsp->bbox[1], x, y, radiusSquared)) - return; - - node = bsp->children[1]; - } - - subsector_t *sub = (subsector_t *)((uint8_t *)node - 1); - callback(sub); -} - -template -void BSPWalkCircle(float x, float y, float radiusSquared, const Callback &callback) -{ - if (level.nodes.Size() == 0) - callback(&level.subsectors[0]); - else - BSPNodeWalkCircle(level.HeadNode(), x, y, radiusSquared, callback); -} - // static so that we build up a reserve (memory allocations stop) // For multithread processing each worker thread needs its own copy, though. static thread_local TArray addedLightsArray; diff --git a/src/polyrenderer/drawers/poly_triangle.cpp b/src/polyrenderer/drawers/poly_triangle.cpp index 992a43d768..0a598ec2f6 100644 --- a/src/polyrenderer/drawers/poly_triangle.cpp +++ b/src/polyrenderer/drawers/poly_triangle.cpp @@ -77,9 +77,9 @@ void PolyTriangleDrawer::SetViewport(const DrawerCommandQueuePtr &queue, int x, queue->Push(viewport_x, viewport_y, viewport_width, viewport_height, dest, dest_width, dest_height, dest_pitch, dest_bgra); } -void PolyTriangleDrawer::SetTransform(const DrawerCommandQueuePtr &queue, const Mat4f *objectToClip) +void PolyTriangleDrawer::SetTransform(const DrawerCommandQueuePtr &queue, const Mat4f *objectToClip, const Mat4f *objectToWorld) { - queue->Push(objectToClip); + queue->Push(objectToClip, objectToWorld); } void PolyTriangleDrawer::SetCullCCW(const DrawerCommandQueuePtr &queue, bool ccw) @@ -114,9 +114,10 @@ void PolyTriangleThreadData::SetViewport(int x, int y, int width, int height, ui weaponScene = false; } -void PolyTriangleThreadData::SetTransform(const Mat4f *newObjectToClip) +void PolyTriangleThreadData::SetTransform(const Mat4f *newObjectToClip, const Mat4f *newObjectToWorld) { objectToClip = newObjectToClip; + objectToWorld = newObjectToWorld; } void PolyTriangleThreadData::DrawElements(const PolyDrawArgs &drawargs) @@ -235,18 +236,30 @@ void PolyTriangleThreadData::DrawArrays(const PolyDrawArgs &drawargs) ShadedTriVertex PolyTriangleThreadData::ShadeVertex(const PolyDrawArgs &drawargs, const TriVertex &v) { // Apply transform to get clip coordinates: - Vec4f position = (*objectToClip) * Vec4f(v.x, v.y, v.z, v.w); + Vec4f objpos = Vec4f(v.x, v.y, v.z, v.w); + Vec4f clippos = (*objectToClip) * objpos; ShadedTriVertex sv; - sv.x = position.X; - sv.y = position.Y; - sv.z = position.Z; - sv.w = position.W; + sv.x = clippos.X; + sv.y = clippos.Y; + sv.z = clippos.Z; + sv.w = clippos.W; sv.u = v.u; sv.v = v.v; - sv.worldX = v.x; - sv.worldY = v.y; - sv.worldZ = v.z; + + if (!objectToWorld) // Identity matrix + { + sv.worldX = v.x; + sv.worldY = v.y; + sv.worldZ = v.z; + } + else + { + Vec4f worldpos = (*objectToWorld) * objpos; + sv.worldX = worldpos.X; + sv.worldY = worldpos.Y; + sv.worldZ = worldpos.Z; + } // Calculate gl_ClipDistance[i] for (int i = 0; i < 3; i++) @@ -576,13 +589,13 @@ PolyTriangleThreadData *PolyTriangleThreadData::Get(DrawerThread *thread) ///////////////////////////////////////////////////////////////////////////// -PolySetTransformCommand::PolySetTransformCommand(const Mat4f *objectToClip) : objectToClip(objectToClip) +PolySetTransformCommand::PolySetTransformCommand(const Mat4f *objectToClip, const Mat4f *objectToWorld) : objectToClip(objectToClip), objectToWorld(objectToWorld) { } void PolySetTransformCommand::Execute(DrawerThread *thread) { - PolyTriangleThreadData::Get(thread)->SetTransform(objectToClip); + PolyTriangleThreadData::Get(thread)->SetTransform(objectToClip, objectToWorld); } ///////////////////////////////////////////////////////////////////////////// diff --git a/src/polyrenderer/drawers/poly_triangle.h b/src/polyrenderer/drawers/poly_triangle.h index c1e4871dbb..a323a941a9 100644 --- a/src/polyrenderer/drawers/poly_triangle.h +++ b/src/polyrenderer/drawers/poly_triangle.h @@ -37,7 +37,7 @@ public: static void SetCullCCW(const DrawerCommandQueuePtr &queue, bool ccw); static void SetTwoSided(const DrawerCommandQueuePtr &queue, bool twosided); static void SetWeaponScene(const DrawerCommandQueuePtr &queue, bool enable); - static void SetTransform(const DrawerCommandQueuePtr &queue, const Mat4f *objectToClip); + static void SetTransform(const DrawerCommandQueuePtr &queue, const Mat4f *objectToClip, const Mat4f *objectToWorld); static bool IsBgra(); }; @@ -48,7 +48,7 @@ public: PolyTriangleThreadData(int32_t core, int32_t num_cores) : core(core), num_cores(num_cores) { } void SetViewport(int x, int y, int width, int height, uint8_t *dest, int dest_width, int dest_height, int dest_pitch, bool dest_bgra); - void SetTransform(const Mat4f *objectToClip); + void SetTransform(const Mat4f *objectToClip, const Mat4f *objectToWorld); void SetCullCCW(bool value) { ccw = value; } void SetTwoSided(bool value) { twosided = value; } void SetWeaponScene(bool value) { weaponScene = value; } @@ -88,6 +88,7 @@ private: bool twosided = false; bool weaponScene = false; const Mat4f *objectToClip = nullptr; + const Mat4f *objectToWorld = nullptr; enum { max_additional_vertices = 16 }; }; @@ -95,13 +96,14 @@ private: class PolySetTransformCommand : public DrawerCommand { public: - PolySetTransformCommand(const Mat4f *objectToClip); + PolySetTransformCommand(const Mat4f *objectToClip, const Mat4f *objectToWorld); void Execute(DrawerThread *thread) override; FString DebugInfo() override { return "PolySetTransform"; } private: const Mat4f *objectToClip; + const Mat4f *objectToWorld; }; class PolySetCullCCWCommand : public DrawerCommand diff --git a/src/polyrenderer/drawers/screen_triangle.cpp b/src/polyrenderer/drawers/screen_triangle.cpp index 5043bf362c..2c63816059 100644 --- a/src/polyrenderer/drawers/screen_triangle.cpp +++ b/src/polyrenderer/drawers/screen_triangle.cpp @@ -480,6 +480,29 @@ void DrawSpanOpt32(int y, int x0, int x1, const TriDrawTriangleArgs *args) worldnormalZ = args->uniforms->Normal().Z; dynlightcolor = args->uniforms->DynLightColor(); + // The normal vector cannot be uniform when drawing models. Calculate and use the face normal: + if (worldnormalX == 0.0f && worldnormalY == 0.0f && worldnormalZ == 0.0f) + { + float dx1 = args->v2->worldX - args->v1->worldX; + float dy1 = args->v2->worldY - args->v1->worldY; + float dz1 = args->v2->worldZ - args->v1->worldZ; + float dx2 = args->v3->worldX - args->v1->worldX; + float dy2 = args->v3->worldY - args->v1->worldY; + float dz2 = args->v3->worldZ - args->v1->worldZ; + worldnormalX = dy1 * dz2 - dz1 * dy2; + worldnormalY = dz1 * dx2 - dx1 * dz2; + worldnormalZ = dx1 * dy2 - dy1 * dx2; + float lensqr = worldnormalX * worldnormalX + worldnormalY * worldnormalY + worldnormalZ * worldnormalZ; +#ifndef NO_SSE + float rcplen = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(lensqr))); +#else + float rcplen = 1.0f / sqrt(lensqr); +#endif + worldnormalX *= rcplen; + worldnormalY *= rcplen; + worldnormalZ *= rcplen; + } + int affineOffset = x0 / 16 * 16 - x0; float posLightW = posW + stepW * affineOffset; posWorldX = posWorldX + stepWorldX * affineOffset; @@ -1067,6 +1090,29 @@ void DrawSpanOpt8(int y, int x0, int x1, const TriDrawTriangleArgs *args) worldnormalZ = args->uniforms->Normal().Z; dynlightcolor = args->uniforms->DynLightColor(); + // The normal vector cannot be uniform when drawing models. Calculate and use the face normal: + if (worldnormalX == 0.0f && worldnormalY == 0.0f && worldnormalZ == 0.0f) + { + float dx1 = args->v2->worldX - args->v1->worldX; + float dy1 = args->v2->worldY - args->v1->worldY; + float dz1 = args->v2->worldZ - args->v1->worldZ; + float dx2 = args->v3->worldX - args->v1->worldX; + float dy2 = args->v3->worldY - args->v1->worldY; + float dz2 = args->v3->worldZ - args->v1->worldZ; + worldnormalX = dy1 * dz2 - dz1 * dy2; + worldnormalY = dz1 * dx2 - dx1 * dz2; + worldnormalZ = dx1 * dy2 - dy1 * dx2; + float lensqr = worldnormalX * worldnormalX + worldnormalY * worldnormalY + worldnormalZ * worldnormalZ; +#ifndef NO_SSE + float rcplen = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(lensqr))); +#else + float rcplen = 1.0f / sqrt(lensqr); +#endif + worldnormalX *= rcplen; + worldnormalY *= rcplen; + worldnormalZ *= rcplen; + } + int affineOffset = x0 / 16 * 16 - x0; float posLightW = posW + stepW * affineOffset; posWorldX = posWorldX + stepWorldX * affineOffset; diff --git a/src/polyrenderer/poly_renderthread.h b/src/polyrenderer/poly_renderthread.h index 92269aff8e..b9a6b32dbc 100644 --- a/src/polyrenderer/poly_renderthread.h +++ b/src/polyrenderer/poly_renderthread.h @@ -32,6 +32,7 @@ class RenderMemory; class PolyTranslucentObject; class PolyDrawSectorPortal; class PolyDrawLinePortal; +class ADynamicLight; class PolyRenderThread { @@ -53,6 +54,8 @@ public: std::vector> SectorPortals; std::vector> LinePortals; + TArray AddedLightsArray; + // Make sure texture can accessed safely void PrepareTexture(FTexture *texture, FRenderStyle style); diff --git a/src/polyrenderer/scene/poly_model.cpp b/src/polyrenderer/scene/poly_model.cpp index 1a5aa842f5..3db3509431 100644 --- a/src/polyrenderer/scene/poly_model.cpp +++ b/src/polyrenderer/scene/poly_model.cpp @@ -36,6 +36,7 @@ void PolyRenderModel(PolyRenderThread *thread, const Mat4f &worldToClip, uint32_t stencilValue, float x, float y, float z, FSpriteModelFrame *smf, AActor *actor) { PolyModelRenderer renderer(thread, worldToClip, stencilValue); + renderer.AddLights(actor); renderer.RenderModel(x, y, z, smf, actor); } @@ -51,6 +52,70 @@ PolyModelRenderer::PolyModelRenderer(PolyRenderThread *thread, const Mat4f &worl { } +void PolyModelRenderer::AddLights(AActor *actor) +{ + if (gl_lights && actor) + { + auto &addedLights = Thread->AddedLightsArray; + + addedLights.Clear(); + + float x = (float)actor->X(); + float y = (float)actor->Y(); + float z = (float)actor->Center(); + float radiusSquared = (float)(actor->renderradius * actor->renderradius); + + BSPWalkCircle(x, y, radiusSquared, [&](subsector_t *subsector) // Iterate through all subsectors potentially touched by actor + { + FLightNode * node = subsector->lighthead; + while (node) // check all lights touching a subsector + { + ADynamicLight *light = node->lightsource; + if (light->visibletoplayer && !(light->flags2&MF2_DORMANT) && (!(light->lightflags&LF_DONTLIGHTSELF) || light->target != actor) && !(light->lightflags&LF_DONTLIGHTACTORS)) + { + int group = subsector->sector->PortalGroup; + DVector3 pos = light->PosRelative(group); + float radius = (float)(light->GetRadius() + actor->renderradius); + double dx = pos.X - x; + double dy = pos.Y - y; + double dz = pos.Z - z; + double distSquared = dx * dx + dy * dy + dz * dz; + if (distSquared < radius * radius) // Light and actor touches + { + if (std::find(addedLights.begin(), addedLights.end(), light) == addedLights.end()) // Check if we already added this light from a different subsector + { + addedLights.Push(light); + } + } + } + node = node->nextLight; + } + }); + + NumLights = addedLights.Size(); + Lights = Thread->FrameMemory->AllocMemory(NumLights); + for (int i = 0; i < NumLights; i++) + { + ADynamicLight *lightsource = addedLights[i]; + + bool is_point_light = (lightsource->lightflags & LF_ATTENUATE) != 0; + + uint32_t red = lightsource->GetRed(); + uint32_t green = lightsource->GetGreen(); + uint32_t blue = lightsource->GetBlue(); + + PolyLight &light = Lights[i]; + light.x = (float)lightsource->X(); + light.y = (float)lightsource->Y(); + light.z = (float)lightsource->Z(); + light.radius = 256.0f / lightsource->GetRadius(); + light.color = (red << 16) | (green << 8) | blue; + if (is_point_light) + light.radius = -light.radius; + } + } +} + void PolyModelRenderer::BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) { ModelActor = actor; @@ -139,8 +204,9 @@ void PolyModelRenderer::SetTransform() swapYZ.Matrix[1 + 2 * 4] = 1.0f; swapYZ.Matrix[2 + 1 * 4] = 1.0f; swapYZ.Matrix[3 + 3 * 4] = 1.0f; + ObjectToWorld = swapYZ * ObjectToWorld; - PolyTriangleDrawer::SetTransform(Thread->DrawQueue, Thread->FrameMemory->NewObject(WorldToClip * swapYZ * ObjectToWorld)); + PolyTriangleDrawer::SetTransform(Thread->DrawQueue, Thread->FrameMemory->NewObject(WorldToClip * ObjectToWorld), Thread->FrameMemory->NewObject(ObjectToWorld)); } void PolyModelRenderer::DrawArrays(int start, int count) @@ -156,6 +222,7 @@ void PolyModelRenderer::DrawArrays(int start, int count) PolyDrawArgs args; args.SetLight(GetColorTable(sector->Colormap, sector->SpecialColors[sector_t::sprites], true), lightlevel, PolyRenderer::Instance()->Light.SpriteGlobVis(foggy), fullbrightSprite); + args.SetLights(Lights, NumLights); args.SetStencilTestValue(StencilValue); args.SetClipPlane(0, PolyClipPlane()); args.SetStyle(ModelActor->RenderStyle, ModelActor->Alpha, ModelActor->fillcolor, ModelActor->Translation, SkinTexture, fullbrightSprite); @@ -178,6 +245,7 @@ void PolyModelRenderer::DrawElements(int numIndices, size_t offset) PolyDrawArgs args; args.SetLight(GetColorTable(sector->Colormap, sector->SpecialColors[sector_t::sprites], true), lightlevel, PolyRenderer::Instance()->Light.SpriteGlobVis(foggy), fullbrightSprite); + args.SetLights(Lights, NumLights); args.SetStencilTestValue(StencilValue); args.SetClipPlane(0, PolyClipPlane()); args.SetStyle(ModelActor->RenderStyle, ModelActor->Alpha, ModelActor->fillcolor, ModelActor->Translation, SkinTexture, fullbrightSprite); diff --git a/src/polyrenderer/scene/poly_model.h b/src/polyrenderer/scene/poly_model.h index 6e6ec57bc1..9d2519e817 100644 --- a/src/polyrenderer/scene/poly_model.h +++ b/src/polyrenderer/scene/poly_model.h @@ -34,6 +34,8 @@ class PolyModelRenderer : public FModelRenderer public: PolyModelRenderer(PolyRenderThread *thread, const Mat4f &worldToClip, uint32_t stencilValue); + void AddLights(AActor *actor); + ModelRendererType GetType() const override { return PolyModelRendererType; } void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override; @@ -61,6 +63,8 @@ public: unsigned int *IndexBuffer = nullptr; TriVertex *VertexBuffer = nullptr; float InterpolationFactor = 0.0; + PolyLight *Lights = nullptr; + int NumLights = 0; }; class PolyModelVertexBuffer : public IModelVertexBuffer diff --git a/src/polyrenderer/scene/poly_scene.cpp b/src/polyrenderer/scene/poly_scene.cpp index cb31e25906..48f03eea39 100644 --- a/src/polyrenderer/scene/poly_scene.cpp +++ b/src/polyrenderer/scene/poly_scene.cpp @@ -104,7 +104,7 @@ void RenderPolyScene::RenderSectors() PolyRenderer::Instance()->Threads.RenderThreadSlices(totalcount, [&](PolyRenderThread *thread) { PolyTriangleDrawer::SetCullCCW(thread->DrawQueue, !CurrentViewpoint->Mirror); - PolyTriangleDrawer::SetTransform(thread->DrawQueue, thread->FrameMemory->NewObject(CurrentViewpoint->WorldToClip)); + PolyTriangleDrawer::SetTransform(thread->DrawQueue, thread->FrameMemory->NewObject(CurrentViewpoint->WorldToClip), nullptr); if (thread != mainthread) { @@ -336,7 +336,7 @@ void RenderPolyScene::RenderPortals() Mat4f *transform = thread->FrameMemory->NewObject(CurrentViewpoint->WorldToClip); PolyTriangleDrawer::SetCullCCW(thread->DrawQueue, !CurrentViewpoint->Mirror); - PolyTriangleDrawer::SetTransform(thread->DrawQueue, transform); + PolyTriangleDrawer::SetTransform(thread->DrawQueue, transform, nullptr); PolyDrawArgs args; args.SetWriteColor(!enterPortals); @@ -379,7 +379,7 @@ void RenderPolyScene::RenderTranslucent() Mat4f *transform = thread->FrameMemory->NewObject(CurrentViewpoint->WorldToClip); PolyTriangleDrawer::SetCullCCW(thread->DrawQueue, !CurrentViewpoint->Mirror); - PolyTriangleDrawer::SetTransform(thread->DrawQueue, transform); + PolyTriangleDrawer::SetTransform(thread->DrawQueue, transform, nullptr); PolyMaskedCycles.Clock(); diff --git a/src/polyrenderer/scene/poly_sky.cpp b/src/polyrenderer/scene/poly_sky.cpp index e8b3faaddf..1fa71ba83a 100644 --- a/src/polyrenderer/scene/poly_sky.cpp +++ b/src/polyrenderer/scene/poly_sky.cpp @@ -83,7 +83,7 @@ void PolySkyDome::Render(PolyRenderThread *thread, const Mat4f &worldToView, con int rc = mRows + 1; - PolyTriangleDrawer::SetTransform(thread->DrawQueue, thread->FrameMemory->NewObject(worldToClip * objectToWorld)); + PolyTriangleDrawer::SetTransform(thread->DrawQueue, thread->FrameMemory->NewObject(worldToClip * objectToWorld), nullptr); PolyDrawArgs args; args.SetLight(&NormalLight, 255, PolyRenderer::Instance()->Light.WallGlobVis(false), true); diff --git a/src/r_data/models/models.h b/src/r_data/models/models.h index bb5fb3b84f..f7f80c4e58 100644 --- a/src/r_data/models/models.h +++ b/src/r_data/models/models.h @@ -499,4 +499,49 @@ public: extern DeletingModelArray Models; +// Check if circle potentially intersects with node AABB +inline bool CheckBBoxCircle(float *bbox, float x, float y, float radiusSquared) +{ + float centerX = (bbox[BOXRIGHT] + bbox[BOXLEFT]) * 0.5f; + float centerY = (bbox[BOXBOTTOM] + bbox[BOXTOP]) * 0.5f; + float extentX = (bbox[BOXRIGHT] - bbox[BOXLEFT]) * 0.5f; + float extentY = (bbox[BOXBOTTOM] - bbox[BOXTOP]) * 0.5f; + float aabbRadiusSquared = extentX * extentX + extentY * extentY; + x -= centerX; + y -= centerY; + float dist = x * x + y * y; + return dist <= radiusSquared + aabbRadiusSquared; +} + +// Helper function for BSPWalkCircle +template +void BSPNodeWalkCircle(void *node, float x, float y, float radiusSquared, const Callback &callback) +{ + while (!((size_t)node & 1)) + { + node_t *bsp = (node_t *)node; + + if (CheckBBoxCircle(bsp->bbox[0], x, y, radiusSquared)) + BSPNodeWalkCircle(bsp->children[0], x, y, radiusSquared, callback); + + if (!CheckBBoxCircle(bsp->bbox[1], x, y, radiusSquared)) + return; + + node = bsp->children[1]; + } + + subsector_t *sub = (subsector_t *)((uint8_t *)node - 1); + callback(sub); +} + +// Search BSP for subsectors within the given radius and call callback(subsector) for each found +template +void BSPWalkCircle(float x, float y, float radiusSquared, const Callback &callback) +{ + if (level.nodes.Size() == 0) + callback(&level.subsectors[0]); + else + BSPNodeWalkCircle(level.HeadNode(), x, y, radiusSquared, callback); +} + #endif diff --git a/src/swrenderer/r_renderthread.h b/src/swrenderer/r_renderthread.h index 34beab6ae3..36189c35d6 100644 --- a/src/swrenderer/r_renderthread.h +++ b/src/swrenderer/r_renderthread.h @@ -28,6 +28,7 @@ class DrawerCommandQueue; typedef std::shared_ptr DrawerCommandQueuePtr; class RenderMemory; +class ADynamicLight; EXTERN_CVAR(Bool, r_models); @@ -75,6 +76,8 @@ namespace swrenderer std::unique_ptr Light; DrawerCommandQueuePtr DrawQueue; + TArray AddedLightsArray; + std::thread thread; // VisibleSprite working buffers diff --git a/src/swrenderer/things/r_model.cpp b/src/swrenderer/things/r_model.cpp index 831fc09f8a..a7e8553a67 100644 --- a/src/swrenderer/things/r_model.cpp +++ b/src/swrenderer/things/r_model.cpp @@ -75,6 +75,7 @@ namespace swrenderer void RenderModel::Render(RenderThread *thread, short *cliptop, short *clipbottom, int minZ, int maxZ, Fake3DTranslucent clip3DFloor) { SWModelRenderer renderer(thread, clip3DFloor, &WorldToClip, MirrorWorldToClip); + renderer.AddLights(actor); renderer.RenderModel(x, y, z, smf, actor); } @@ -93,6 +94,70 @@ namespace swrenderer { } + void SWModelRenderer::AddLights(AActor *actor) + { + if (gl_lights && actor) + { + auto &addedLights = Thread->AddedLightsArray; + + addedLights.Clear(); + + float x = (float)actor->X(); + float y = (float)actor->Y(); + float z = (float)actor->Center(); + float radiusSquared = (float)(actor->renderradius * actor->renderradius); + + BSPWalkCircle(x, y, radiusSquared, [&](subsector_t *subsector) // Iterate through all subsectors potentially touched by actor + { + FLightNode * node = subsector->lighthead; + while (node) // check all lights touching a subsector + { + ADynamicLight *light = node->lightsource; + if (light->visibletoplayer && !(light->flags2&MF2_DORMANT) && (!(light->lightflags&LF_DONTLIGHTSELF) || light->target != actor) && !(light->lightflags&LF_DONTLIGHTACTORS)) + { + int group = subsector->sector->PortalGroup; + DVector3 pos = light->PosRelative(group); + float radius = (float)(light->GetRadius() + actor->renderradius); + double dx = pos.X - x; + double dy = pos.Y - y; + double dz = pos.Z - z; + double distSquared = dx * dx + dy * dy + dz * dz; + if (distSquared < radius * radius) // Light and actor touches + { + if (std::find(addedLights.begin(), addedLights.end(), light) == addedLights.end()) // Check if we already added this light from a different subsector + { + addedLights.Push(light); + } + } + } + node = node->nextLight; + } + }); + + NumLights = addedLights.Size(); + Lights = Thread->FrameMemory->AllocMemory(NumLights); + for (int i = 0; i < NumLights; i++) + { + ADynamicLight *lightsource = addedLights[i]; + + bool is_point_light = (lightsource->lightflags & LF_ATTENUATE) != 0; + + uint32_t red = lightsource->GetRed(); + uint32_t green = lightsource->GetGreen(); + uint32_t blue = lightsource->GetBlue(); + + PolyLight &light = Lights[i]; + light.x = (float)lightsource->X(); + light.y = (float)lightsource->Y(); + light.z = (float)lightsource->Z(); + light.radius = 256.0f / lightsource->GetRadius(); + light.color = (red << 16) | (green << 8) | blue; + if (is_point_light) + light.radius = -light.radius; + } + } + } + void SWModelRenderer::BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) { ModelActor = actor; @@ -235,8 +300,9 @@ namespace swrenderer swapYZ.Matrix[1 + 2 * 4] = 1.0f; swapYZ.Matrix[2 + 1 * 4] = 1.0f; swapYZ.Matrix[3 + 3 * 4] = 1.0f; + ObjectToWorld = swapYZ * ObjectToWorld; - PolyTriangleDrawer::SetTransform(Thread->DrawQueue, Thread->FrameMemory->NewObject((*WorldToClip) * swapYZ * ObjectToWorld)); + PolyTriangleDrawer::SetTransform(Thread->DrawQueue, Thread->FrameMemory->NewObject((*WorldToClip) * ObjectToWorld), Thread->FrameMemory->NewObject(ObjectToWorld)); } void SWModelRenderer::DrawArrays(int start, int count) @@ -252,6 +318,8 @@ namespace swrenderer PolyDrawArgs args; args.SetLight(GetColorTable(sector->Colormap, sector->SpecialColors[sector_t::sprites], true), lightlevel, Thread->Light->SpriteGlobVis(foggy), fullbrightSprite); + args.SetLights(Lights, NumLights); + args.SetNormal(FVector3(0.0f, 0.0f, 0.0f)); args.SetStyle(ModelActor->RenderStyle, ModelActor->Alpha, ModelActor->fillcolor, ModelActor->Translation, SkinTexture, fullbrightSprite); args.SetDepthTest(true); args.SetWriteDepth(true); @@ -276,6 +344,8 @@ namespace swrenderer PolyDrawArgs args; args.SetLight(GetColorTable(sector->Colormap, sector->SpecialColors[sector_t::sprites], true), lightlevel, Thread->Light->SpriteGlobVis(foggy), fullbrightSprite); + args.SetLights(Lights, NumLights); + args.SetNormal(FVector3(0.0f, 0.0f, 0.0f)); args.SetStyle(ModelActor->RenderStyle, ModelActor->Alpha, ModelActor->fillcolor, ModelActor->Translation, SkinTexture, fullbrightSprite); args.SetDepthTest(true); args.SetWriteDepth(true); diff --git a/src/swrenderer/things/r_model.h b/src/swrenderer/things/r_model.h index 2c08c95c10..b2db91c07e 100644 --- a/src/swrenderer/things/r_model.h +++ b/src/swrenderer/things/r_model.h @@ -28,6 +28,8 @@ #include "swrenderer/r_renderthread.h" #include "swrenderer/things/r_visiblesprite.h" +struct PolyLight; + namespace swrenderer { void RenderHUDModel(RenderThread *thread, DPSprite *psp, float ofsx, float ofsy); @@ -56,6 +58,8 @@ namespace swrenderer public: SWModelRenderer(RenderThread *thread, Fake3DTranslucent clip3DFloor, Mat4f *worldToClip, bool mirrorWorldToClip); + void AddLights(AActor *actor); + ModelRendererType GetType() const override { return SWModelRendererType; } void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override; @@ -85,6 +89,8 @@ namespace swrenderer float InterpolationFactor = 0.0; Mat4f *WorldToClip = nullptr; bool MirrorWorldToClip = false; + PolyLight *Lights = nullptr; + int NumLights = 0; }; class SWModelVertexBuffer : public IModelVertexBuffer