From e3542b59f77a003b403a7eb1b60a81eaf1e38eca Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 4 Jun 2026 15:14:18 +0300 Subject: [PATCH] Working on lights --- resources/config2/lights_dorm.json | 82 +++++++++++++ resources/config2/lights_uni_exterior.json | 17 +++ resources/config2/lights_uni_interior.json | 17 +++ src/Game.cpp | 22 ++-- src/Location.cpp | 130 +++++++++++++++++---- src/Location.h | 5 + 6 files changed, 237 insertions(+), 36 deletions(-) create mode 100644 resources/config2/lights_dorm.json create mode 100644 resources/config2/lights_uni_exterior.json create mode 100644 resources/config2/lights_uni_interior.json diff --git a/resources/config2/lights_dorm.json b/resources/config2/lights_dorm.json new file mode 100644 index 0000000..86867b1 --- /dev/null +++ b/resources/config2/lights_dorm.json @@ -0,0 +1,82 @@ +{ + "lights": [ + { + "id" : "main_hall", + "positionX": -3.5951, + "positionY": 6.0, + "positionZ": 0.280929, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 4.0, + "colorG": 3.2, + "colorB": 2.4 + }, + { + "id" : "player_room", + "autoLight": true, + "autoLightDistance": 6.0, + "positionX": 4.92215, + "positionY": 4.0, + "positionZ": -14.62, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 2.0, + "colorG": 1.6, + "colorB": 1.2 + }, + { + "id" : "laundry", + "autoLight": true, + "autoLightDistance": 6.0, + "positionX": 4.65392, + "positionY": 4.0, + "positionZ": -20.0, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 2.0, + "colorG": 1.6, + "colorB": 1.2 + }, + { + "id" : "kitchen", + "autoLight": true, + "autoLightDistance": 6.0, + "positionX": -5.21995, + "positionY": 4.0, + "positionZ": -20, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 2.0, + "colorG": 1.6, + "colorB": 1.2 + }, + { + "id" : "streetlight1", + "positionX": -14.5, + "positionY": 4.0, + "positionZ": -14, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 2.0, + "colorG": 1.6, + "colorB": 1.2 + }, + { + "id" : "streetlight2", + "positionX": -14.5, + "positionY": 4.0, + "positionZ": 14.0, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 2.0, + "colorG": 1.6, + "colorB": 1.2 + } + ] +} diff --git a/resources/config2/lights_uni_exterior.json b/resources/config2/lights_uni_exterior.json new file mode 100644 index 0000000..0d78700 --- /dev/null +++ b/resources/config2/lights_uni_exterior.json @@ -0,0 +1,17 @@ +{ + "lights": [ + { + "id": "lamp_entrance", + "positionX": 6.6335, + "positionY": 2.9, + "positionZ": -9.3, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 4.0, + "colorG": 3.2, + "colorB": 2.4, + "autoLight": false + } + ] +} diff --git a/resources/config2/lights_uni_interior.json b/resources/config2/lights_uni_interior.json new file mode 100644 index 0000000..9ebf8eb --- /dev/null +++ b/resources/config2/lights_uni_interior.json @@ -0,0 +1,17 @@ +{ + "lights": [ + { + "id": "lamp_library", + "positionX": 3.76222, + "positionY": 5.0, + "positionZ": 4.16035, + "directionX": 0.0, + "directionY": -1.0, + "directionZ": 0.0, + "colorR": 4.0, + "colorG": 3.2, + "colorB": 2.4, + "autoLight": false + } + ] +} diff --git a/src/Game.cpp b/src/Game.cpp index c892d2b..d93b8e2 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -262,6 +262,7 @@ namespace ZL uniInteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_interior.json"; uniInteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_interior.json"; + uniInteriorParams.lightsJsonPath = "resources/config2/lights_uni_interior.json"; uniInteriorParams.scriptPath = "resources/start_uni_interior.lua"; uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior.json"; uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104); @@ -288,6 +289,7 @@ namespace ZL //uniExteriorParams.navigationJsonPaths = { "resources/navigation/uni_exterior2_all.json" }; uniExteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_exterior.json"; uniExteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_exterior.json"; + uniExteriorParams.lightsJsonPath = "resources/config2/lights_uni_exterior.json"; uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua"; uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4); uniExteriorParams.npcsJsonPath = "resources/config2/npcs_uni_exterior.json"; @@ -308,6 +310,7 @@ namespace ZL params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json"; params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues.json"; + /* params_dorm.navigationJsonPaths = { "resources/navigation/dorm3_bca.json", "resources/navigation/dorm3_ca.json", @@ -315,7 +318,7 @@ namespace ZL "resources/navigation/dorm3_a.json", "resources/navigation/dorm3_b.json", "resources/navigation/dorm3_all_open.json", - }; + };*/ /* params_dorm.navigationJsonPaths = { "resources/navigation/dorm2_bca2.json", @@ -336,7 +339,7 @@ namespace ZL "resources/navigation/dorm2_b.txt", "resources/navigation/dorm2_all_open.txt", };*/ - /* + params_dorm.navigationJsonPaths = { "resources/navigation/dorm0_large.json", "resources/navigation/dorm0_large.json", @@ -344,11 +347,12 @@ namespace ZL "resources/navigation/dorm0_large.json", "resources/navigation/dorm0_large.json", "resources/navigation/dorm0_large.json", - };*/ + }; params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json"; params_dorm.triggerZonesJsonPath = "resources/config2/trigger_zones_dorm.json"; + params_dorm.lightsJsonPath = "resources/config2/lights_dorm.json"; params_dorm.scriptPath = "resources/start_dorm.lua"; params_dorm.interactiveObjectsJsonPath = "resources/config2/interactive_objects_dorm.json"; params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022); @@ -422,18 +426,6 @@ namespace ZL }); } - // Add test spotlight to every location for night-mode testing. - { - PointLight testLight; - //testLight.position = Eigen::Vector3f(-3.5951f, 3.0f, 0.280929f); - testLight.position = Eigen::Vector3f(-3.5951f, 6.0f, 0.280929f); - testLight.direction = Eigen::Vector3f(0.0f, -1.0f, 0.0f); // straight down - //testLight.color = Eigen::Vector3f(2.0f, 1.6f, 1.2f); - testLight.color = Eigen::Vector3f(4.0f, 3.2f, 2.4f); - for (auto& [name, loc] : locations) - loc->pointLights.push_back(testLight); - } - currentLocation = locations["location_dorm"]; currentLocation->scriptEngine.callLocationEnterCallback(); diff --git a/src/Location.cpp b/src/Location.cpp index 482c006..fd94ecf 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -118,6 +118,7 @@ namespace ZL loadTeleportZones(params.teleportsJsonPath, CONST_ZIP_FILE); loadTriggerZones(params.triggerZonesJsonPath, CONST_ZIP_FILE); + loadPointLights(params.lightsJsonPath, CONST_ZIP_FILE); #ifndef EMSCRIPTEN // Create shadow map (2048x2048, ortho size 40, near 0.1, far 100) @@ -274,6 +275,64 @@ namespace ZL std::cout << "[TRIGGER] Loaded " << triggerZones.size() << " trigger zone(s) from " << jsonPath << std::endl; } + void Location::loadPointLights(const std::string& jsonPath, const char* zipFile) + { + if (jsonPath.empty()) return; + + std::string content; + try { + if (!zipFile || zipFile[0] == '\0') { + content = readTextFile(jsonPath); + } else { + auto buf = readFileFromZIP(jsonPath, zipFile); + if (buf.empty()) { + std::cerr << "[LIGHTS] Failed to read " << jsonPath << " from zip" << std::endl; + return; + } + content.assign(buf.begin(), buf.end()); + } + } catch (const std::exception& e) { + std::cerr << "[LIGHTS] Failed to open " << jsonPath << ": " << e.what() << std::endl; + return; + } + + using json = nlohmann::json; + json j; + try { + j = json::parse(content); + } catch (const std::exception& e) { + std::cerr << "[LIGHTS] JSON parse error in " << jsonPath << ": " << e.what() << std::endl; + return; + } + + if (!j.contains("lights") || !j["lights"].is_array()) return; + + for (const auto& item : j["lights"]) { + PointLight pl; + pl.id = item.value("id", ""); + pl.autoLight = item.value("autoLight", false); + pl.autoLightDistance = item.value("autoLightDistance", -1.0f); + pl.position = Eigen::Vector3f( + item.value("positionX", 0.0f), + item.value("positionY", 0.0f), + item.value("positionZ", 0.0f) + ); + pl.direction = Eigen::Vector3f( + item.value("directionX", 0.0f), + item.value("directionY", -1.0f), + item.value("directionZ", 0.0f) + ); + pl.color = Eigen::Vector3f( + item.value("colorR", 1.0f), + item.value("colorG", 1.0f), + item.value("colorB", 1.0f) + ); + pointLights.push_back(std::move(pl)); + } + + std::cout << "[LIGHTS] Loaded " << pointLights.size() << " light(s) from " << jsonPath << std::endl; + } + void Location::updateTriggerZones(const Eigen::Vector3f& playerPos) { for (auto& tz : triggerZones) { @@ -918,13 +977,42 @@ namespace ZL void Location::drawGameNight() { - // --- Find nearest spotlight for shadow casting --- - const PointLight* shadowLight = nullptr; + // --- Determine which lights are active this frame --- + // autoLight=true lights only activate when they are the nearest light to the player. + // autoLight=false lights are always active. + int nearestLightIdx = -1; if (player && !pointLights.empty()) { float minDist = FLT_MAX; - for (const auto& pl : pointLights) { - const float d = (pl.position - player->position).norm(); - if (d < minDist) { minDist = d; shadowLight = &pl; } + for (int i = 0; i < static_cast(pointLights.size()); ++i) { + const float d = (pointLights[i].position - player->position).norm(); + if (d < minDist) { minDist = d; nearestLightIdx = i; } + } + } + + static constexpr int MAX_POINT_LIGHTS = 4; + std::vector activeLightIndices; + activeLightIndices.reserve(pointLights.size()); + for (int i = 0; i < static_cast(pointLights.size()); ++i) { + const PointLight& pl = pointLights[i]; + bool active = !pl.autoLight; + if (!active && i == nearestLightIdx) { + // autoLight: also check distance threshold if set + const float dist = player ? (pl.position - player->position).norm() : 0.0f; + active = (pl.autoLightDistance <= 0.0f || dist <= pl.autoLightDistance); + } + if (active) { + activeLightIndices.push_back(i); + if (static_cast(activeLightIndices.size()) >= MAX_POINT_LIGHTS) break; + } + } + + // Shadow caster = nearest active light to player + const PointLight* shadowLight = nullptr; + if (player && !activeLightIndices.empty()) { + float minDist = FLT_MAX; + for (int idx : activeLightIndices) { + const float d = (pointLights[idx].position - player->position).norm(); + if (d < minDist) { minDist = d; shadowLight = &pointLights[idx]; } } } const bool hasShadows = (shadowMap != nullptr && shadowLight != nullptr); @@ -966,26 +1054,26 @@ namespace ZL cameraViewMatrix = renderer.GetCurrentModelViewMatrix(); - // Transform spot lights to eye space and build uniform arrays (max 4) - static constexpr int MAX_POINT_LIGHTS = 4; + // Transform active spot lights to eye space and build uniform arrays float plPosEye[MAX_POINT_LIGHTS * 3] = {}; float plDirEye[MAX_POINT_LIGHTS * 3] = {}; float plColors[MAX_POINT_LIGHTS * 3] = {}; const Eigen::Matrix3f camRot = cameraViewMatrix.block<3, 3>(0, 0); - const int lightCount = min(static_cast(pointLights.size()), MAX_POINT_LIGHTS); - for (int i = 0; i < lightCount; ++i) { - const Eigen::Vector4f worldPos(pointLights[i].position.x(), pointLights[i].position.y(), pointLights[i].position.z(), 1.0f); + const int lightCount = static_cast(activeLightIndices.size()); + for (int j = 0; j < lightCount; ++j) { + const PointLight& pl = pointLights[activeLightIndices[j]]; + const Eigen::Vector4f worldPos(pl.position.x(), pl.position.y(), pl.position.z(), 1.0f); const Eigen::Vector4f eyePos4 = cameraViewMatrix * worldPos; - plPosEye[i * 3 + 0] = eyePos4.x(); - plPosEye[i * 3 + 1] = eyePos4.y(); - plPosEye[i * 3 + 2] = eyePos4.z(); - const Eigen::Vector3f eyeDir = camRot * pointLights[i].direction; - plDirEye[i * 3 + 0] = eyeDir.x(); - plDirEye[i * 3 + 1] = eyeDir.y(); - plDirEye[i * 3 + 2] = eyeDir.z(); - plColors[i * 3 + 0] = pointLights[i].color.x(); - plColors[i * 3 + 1] = pointLights[i].color.y(); - plColors[i * 3 + 2] = pointLights[i].color.z(); + plPosEye[j * 3 + 0] = eyePos4.x(); + plPosEye[j * 3 + 1] = eyePos4.y(); + plPosEye[j * 3 + 2] = eyePos4.z(); + const Eigen::Vector3f eyeDir = camRot * pl.direction; + plDirEye[j * 3 + 0] = eyeDir.x(); + plDirEye[j * 3 + 1] = eyeDir.y(); + plDirEye[j * 3 + 2] = eyeDir.z(); + plColors[j * 3 + 0] = pl.color.x(); + plColors[j * 3 + 1] = pl.color.y(); + plColors[j * 3 + 2] = pl.color.z(); } // Compute light-from-camera matrix for shadow lookup @@ -993,7 +1081,7 @@ namespace ZL if (hasShadows) { lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewMatrix.inverse(); renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data()); - // Pass first light's eye-space direction as uLightDir for shadow bias + // Pass first active light's eye-space direction as uLightDir for shadow bias renderer.RenderUniform3fv("uLightDir", plDirEye); } diff --git a/src/Location.h b/src/Location.h index f056c5d..605fc2d 100644 --- a/src/Location.h +++ b/src/Location.h @@ -22,9 +22,12 @@ namespace ZL struct PointLight { + std::string id; Eigen::Vector3f position; // world space Eigen::Vector3f direction; // world space, unit vector the cone points toward Eigen::Vector3f color; // RGB, values > 1.0 increase intensity + bool autoLight = false; // if true, only activates when nearest light to player + float autoLightDistance = -1.0f; // max distance for autoLight activation; <=0 means unlimited }; struct TriggerZone @@ -47,6 +50,7 @@ namespace ZL std::string scriptPath; std::string teleportsJsonPath; std::string triggerZonesJsonPath; + std::string lightsJsonPath; Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); }; @@ -159,6 +163,7 @@ namespace ZL void updateDynamicReplans(int64_t deltaMs); void loadTeleportZones(const std::string& jsonPath, const char* zipFile); void loadTriggerZones(const std::string& jsonPath, const char* zipFile); + void loadPointLights(const std::string& jsonPath, const char* zipFile); void updateTriggerZones(const Eigen::Vector3f& playerPos); void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom); int findNpcIndex(const Character* npc) const;