Working on lights

This commit is contained in:
Vladislav Khorev 2026-06-04 15:14:18 +03:00
parent 442d3a7945
commit e3542b59f7
6 changed files with 237 additions and 36 deletions

View File

@ -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
}
]
}

View File

@ -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
}
]
}

View File

@ -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
}
]
}

View File

@ -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();

View File

@ -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<int>(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<int> activeLightIndices;
activeLightIndices.reserve(pointLights.size());
for (int i = 0; i < static_cast<int>(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<int>(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<int>(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<int>(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);
}

View File

@ -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;