From eecb8d3459166eee67c89064d6fbdd1bb372d489 Mon Sep 17 00:00:00 2001 From: Caner Karaca Date: Wed, 7 Oct 2026 23:26:55 +0300 Subject: [PATCH] feat(lights): add shadow offset configuration support in JSONC --- src/ModelExtrasAPI.cpp | 19 ++++++++++++++++++- src/ModelExtrasAPI.h | 1 + src/features/core/dummy.cpp | 37 +++++++++++++++++++++++++++++++------ src/utils/render.cpp | 7 +++++-- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/ModelExtrasAPI.cpp b/src/ModelExtrasAPI.cpp index 8135a801..41eae2c5 100755 --- a/src/ModelExtrasAPI.cpp +++ b/src/ModelExtrasAPI.cpp @@ -437,5 +437,22 @@ float ME_GetSirenPointLightMul() { return LightsConfig::Get().fSirenPointLightMul; } -} // extern "C" +bool ME_GetLightShadowOffset(int modelIndex, const char *dummyName, float *outOffsetX, float *outOffsetY) { + const auto *pJson = DataMgr::Find(modelIndex); + if (!pJson || !pJson->contains("lights") || !dummyName) return false; + const auto &l = (*pJson)["lights"]; + if (!l.contains(dummyName)) return false; + const auto &d = l[dummyName]; + if (!d.contains("shadow")) return false; + const auto &sh = d["shadow"]; + if (!sh.contains("offset") && !sh.contains("shadow_offset")) return false; + const auto &off = sh.contains("offset") ? sh["offset"] : sh["shadow_offset"]; + if (off.is_array() && off.size() >= 2) { + if (outOffsetX) *outOffsetX = off[0].get(); + if (outOffsetY) *outOffsetY = off[1].get(); + return true; + } + return false; +} +} // extern "C" \ No newline at end of file diff --git a/src/ModelExtrasAPI.h b/src/ModelExtrasAPI.h index dda46ab5..4fccb099 100755 --- a/src/ModelExtrasAPI.h +++ b/src/ModelExtrasAPI.h @@ -217,6 +217,7 @@ extern "C" // Visual & Render Configuration ME_WRAPPER float ME_GetPointLightIntensity(); ME_WRAPPER float ME_GetSirenPointLightMul(); + ME_WRAPPER bool ME_GetLightShadowOffset(int modelIndex, const char *dummyName, float *outOffsetX, float *outOffsetY); #ifdef __cplusplus } diff --git a/src/features/core/dummy.cpp b/src/features/core/dummy.cpp index f15190c6..d9a0d3b2 100755 --- a/src/features/core/dummy.cpp +++ b/src/features/core/dummy.cpp @@ -78,18 +78,21 @@ VehicleDummy::VehicleDummy(const DummyConfig& config) if (jsonData.contains("lights")) { std::string newName(name.substr(0, name.find("_prm"))); + const char* specKey = LightManager::GetLightSpecificKey(data.lightType); + const char* fallbackKey = LightManager::GetLightGroupKey(data.lightType); const nlohmann::json* pLightsSec = nullptr; + if (jsonData["lights"].contains(newName)) { pLightsSec = &jsonData["lights"][newName]; } - else + else if (specKey && jsonData["lights"].contains(specKey)) { - const char* fallbackKey = LightManager::GetLightGroupKey(data.lightType); - if (fallbackKey && jsonData["lights"].contains(fallbackKey)) - { - pLightsSec = &jsonData["lights"][fallbackKey]; - } + pLightsSec = &jsonData["lights"][specKey]; + } + else if (fallbackKey && jsonData["lights"].contains(fallbackKey)) + { + pLightsSec = &jsonData["lights"][fallbackKey]; } if (pLightsSec) @@ -125,6 +128,28 @@ VehicleDummy::VehicleDummy(const DummyConfig& config) data.shadow.texture = shadow.value("texture", ""); data.shadow.rotationChecks = shadow.value("rotationchecks", true); + if (shadow.contains("offset")) + { + const auto &off = shadow["offset"]; + if (off.is_number()) + { + data.shadow.offset.y = off.get(); + } + else if (off.is_object()) + { + data.shadow.offset.x = off.value("x", data.shadow.offset.x); + data.shadow.offset.y = off.value("y", data.shadow.offset.y); + } + else if (off.is_array() && off.size() >= 1) + { + data.shadow.offset.x = off[0].get(); + if (off.size() >= 2) + { + data.shadow.offset.y = off[1].get(); + } + } + } + // shadows will be force enabled if there is JSON data for it. data.shadow.render = true; } diff --git a/src/utils/render.cpp b/src/utils/render.cpp index 3662616b..35a9023f 100755 --- a/src/utils/render.cpp +++ b/src/utils/render.cpp @@ -535,8 +535,11 @@ void RenderUtil::RegisterShadowDirectional(const DummyConfig *pConfig, const std // Scale shadow size/thickness with inertia factor so it expands/contracts naturally float currentShdwSz = shdwSz * (0.35f + 0.65f * std::clamp(alphaMul, 0.0f, 1.0f)); - // Push shadow forward along light direction - CVector shdwCenter = worldPos + lightDir * (currentShdwSz * SHDW_SZ_MUL + 0.2f); + // Push shadow forward along light direction (with optional JSON shadow.offset: offset.y along lightDir, offset.x along rightDir) + float forwardPush = currentShdwSz * SHDW_SZ_MUL + 0.2f + pConfig->shadow.offset.y; + float sidePush = pConfig->shadow.offset.x; + + CVector shdwCenter = worldPos + lightDir * forwardPush + rightDir * sidePush; CVector2D shdwFront(lightDir.x * (currentShdwSz * SHDW_SZ_MUL), lightDir.y * (currentShdwSz * SHDW_SZ_MUL)); CVector2D shdwSide(rightDir.x * currentShdwSz, rightDir.y * currentShdwSz);