From aceb051c2a16e991cb8f91595a9bb86338667d47 Mon Sep 17 00:00:00 2001 From: Tokeiburu Date: Sat, 7 Dec 2024 13:59:30 -0500 Subject: [PATCH 1/3] Added undo/redo after calculating lightmaps. Changed default colormap option to smooth. Optimized the lightmap calculations, expected to be 10~20 times faster. Changed the shadow calculation from an additive model to a negative model (can be changed, by default it will use the previous formula to avoid compatibily issues). Changed the default light settings: - The falloff style was changed to a new function 'S Curve' instead of linear. This produces better result to official lights (still not 100% accurate though). Added a new 'Light Edit' menu for the Edit Mode. Fixed Magic falloff style formula. Fixed the sun direction if matching the RSW settings. When moving a light, it will now keep its current height relative to the ground rather than sticking to the ground. Added a 'Quick preview' option when selecting a light. This option renders the light (except the sun) using the shader for instant result. It's not perfect, but it can be a big help when picking a light setting. --- BrowEdit3.vcxproj | 2 + BrowEdit3.vcxproj.filters | 6 + browedit/BrowEdit.h | 8 +- browedit/HotkeyActions.cpp | 2 +- browedit/Lightmapper.cpp | 227 +++++++++++++---- browedit/Lightmapper.h | 34 ++- browedit/Map.cpp | 2 +- browedit/MapView.Objectmode.cpp | 45 +++- browedit/MapView.cpp | 47 +++- browedit/MapView.h | 6 +- browedit/actions/LightmapChangeAction.cpp | 78 ++++++ browedit/actions/LightmapChangeAction.h | 23 ++ browedit/components/Gnd.cpp | 257 +++++++++++++++++--- browedit/components/Gnd.h | 3 +- browedit/components/GndRenderer.cpp | 24 ++ browedit/components/GndRenderer.h | 1 + browedit/components/Rsw.Light.cpp | 25 +- browedit/components/Rsw.cpp | 90 +++++-- browedit/components/Rsw.h | 14 +- browedit/gl/Shader.h | 3 +- browedit/math/Ray.cpp | 47 ++-- browedit/shaders/GndShader.h | 44 ++++ browedit/util/Util.cpp | 15 ++ browedit/util/Util.h | 1 + browedit/windows/LightmapSettingsWindow.cpp | 1 + browedit/windows/MenuBar.cpp | 56 +++++ browedit/windows/ObjectSelectWindow.cpp | 10 +- data/shaders/gnd.fs | 190 ++++++++++++++- data/shaders/gnd.vs | 2 + 29 files changed, 1101 insertions(+), 162 deletions(-) create mode 100644 browedit/actions/LightmapChangeAction.cpp create mode 100644 browedit/actions/LightmapChangeAction.h diff --git a/BrowEdit3.vcxproj b/BrowEdit3.vcxproj index a5977732..70d5353e 100644 --- a/BrowEdit3.vcxproj +++ b/BrowEdit3.vcxproj @@ -18,6 +18,7 @@ + @@ -144,6 +145,7 @@ + diff --git a/BrowEdit3.vcxproj.filters b/BrowEdit3.vcxproj.filters index 1e96a1e6..365702b7 100644 --- a/BrowEdit3.vcxproj.filters +++ b/BrowEdit3.vcxproj.filters @@ -430,6 +430,9 @@ browedit\actions + + browedit\actions + @@ -741,6 +744,9 @@ browedit\actions + + browedit\actions + diff --git a/browedit/BrowEdit.h b/browedit/BrowEdit.h index b7c11082..0e5e69fe 100644 --- a/browedit/BrowEdit.h +++ b/browedit/BrowEdit.h @@ -247,7 +247,7 @@ class BrowEdit } textureBrushMode = TextureBrushMode::Stamp; TextureBrushMode brushModeBeforeDropper; - bool heightDoodle = true; + bool heightDoodle = false; std::map> tagList; // tag -> [ file ], utf8 std::map> tagListReverse; // file -> [ tag ], kr @@ -258,7 +258,11 @@ class BrowEdit Config config; std::vector> newNodes; glm::vec3 newNodesCenter; - bool newNodeHeight = false; + enum { + Ground, // The new node will be relative to the ground + Absolute, // The new node position will be determined by newNodesCenter + Relative // The new node will be relative to the ground + newNodesCenter + } newNodePlacement = Ground; std::vector newCubes; std::vector newGatCubes; int pasteOptions = -1; diff --git a/browedit/HotkeyActions.cpp b/browedit/HotkeyActions.cpp index 5bbfe846..4e251082 100644 --- a/browedit/HotkeyActions.cpp +++ b/browedit/HotkeyActions.cpp @@ -77,7 +77,7 @@ void BrowEdit::registerActions() else if (editMode == EditMode::Gat && !heightDoodle) pasteGat(); }, hasActiveMapView); - HotkeyRegistry::registerAction(HotkeyAction::Global_PasteChangeHeight, [this]() { if (editMode == EditMode::Object) { newNodeHeight = !newNodeHeight; } }, hasActiveMapView); + HotkeyRegistry::registerAction(HotkeyAction::Global_PasteChangeHeight, [this]() { if (editMode == EditMode::Object) { newNodePlacement = newNodePlacement == BrowEdit::Absolute ? BrowEdit::Ground : BrowEdit::Absolute; } }, hasActiveMapView); HotkeyRegistry::registerAction(HotkeyAction::Global_ClearZeroHeightWalls, [this]() { activeMapView->map->rootNode->getComponent()->removeZeroHeightWalls(); }, hasActiveMapView); HotkeyRegistry::registerAction(HotkeyAction::Global_CalculateQuadtree, [this]() { activeMapView->map->recalculateQuadTree(this); }, hasActiveMapView); diff --git a/browedit/Lightmapper.cpp b/browedit/Lightmapper.cpp index 5ade56a3..efce3314 100644 --- a/browedit/Lightmapper.cpp +++ b/browedit/Lightmapper.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,7 @@ extern std::mutex debugPointMutex; extern std::vector> debugPoints; double startTime; +LightmapChangeAction* lightmapChangeAction; Lightmapper::Lightmapper(Map* map, BrowEdit* browEdit) : map(map), browEdit(browEdit) { @@ -48,6 +50,10 @@ void Lightmapper::begin() std::cout << "Before:\t" << gnd->tiles.size() << " tiles, " << gnd->lightmaps.size() << " lightmaps" << std::endl; gnd->makeLightmapsUnique(); std::cout << "After:\t" << gnd->tiles.size() << " tiles, " << gnd->lightmaps.size() << " lightmaps" << std::endl; + + lightmapChangeAction = new LightmapChangeAction(); + lightmapChangeAction->setPreviousData(gnd->lightmaps, gnd->tiles); + map->rootNode->getComponent()->setChunksDirty(); map->rootNode->getComponent()->gndShadowDirty = true; @@ -92,24 +98,61 @@ void Lightmapper::run() setProgressText("Gathering Lights"); lights.clear(); models.clear(); + quadtree.clear(); + + auto rsw = map->rootNode->getComponent(); + auto gnd = map->rootNode->getComponent(); + + quadtree.resize(gnd->width, std::vector(gnd->height + 1)); + + for (int x = 0; x < quadtree.size(); x++) { + for (int y = 0; y < quadtree[0].size(); y++) { + quadtree[x][y].range[0].x = 10.0f * x; + quadtree[x][y].range[0].y = 10.0f * y; + quadtree[x][y].range[1].x = 10.0f * (x + 1); + quadtree[x][y].range[1].y = 10.0f * (y + 1); + } + } + map->rootNode->traverse([&](Node* n) { - if (n->getComponent()) - lights.push_back(n); + if (n->getComponent()) { + struct Lightmapper::light_data l; + l.light = n; + l.rswLight = n->getComponent(); + l.rswObject = n->getComponent(); + lights.push_back(l); + } if (RswModel* m = n->getComponent()) - if(m->shadowStrength > 0) - models.push_back(n); + if (m->shadowStrength > 0) { + struct Lightmapper::light_model nmodel = {}; + nmodel.node = n; + nmodel.rswModel = m; + nmodel.collider = n->getComponent(); + models.push_back(nmodel); + nmodel.collider->calculateWorldFaces(); + + // Adds the model to all zones where it collides in the quadtree, on the XZ plane (the Y axis is not used) + int xMin = glm::max(0, (int)(m->aabb.bounds[0].x / 10.0f)); + int yMin = glm::max(0, (int)(m->aabb.bounds[0].z / 10.0f)); + int xMax = glm::min((int)glm::ceil(m->aabb.bounds[1].x / 10.0f), (int)quadtree.size() - 1); + int yMax = glm::min((int)glm::ceil(m->aabb.bounds[1].z / 10.0f), (int)quadtree[0].size() - 1); + + for (int x = xMin; x <= xMax; x++) { + for (int y = yMin; y <= yMax; y++) { + quadtree[x][y].models.push_back(nmodel); + } + } + } }); - auto rsw = map->rootNode->getComponent(); - auto gnd = map->rootNode->getComponent(); std::cout << "Lightmapper: Complexity " << rsw->lightmapSettings.quality << "*"<< rsw->lightmapSettings.quality<<"*" << gnd->width << "*" << gnd->height << "*" << lights.size() << "*" << models.size() << "=" << rsw->lightmapSettings.quality * rsw->lightmapSettings.quality * gnd->width * gnd->height * lights.size() * models.size() << std::endl; auto& settings = rsw->lightmapSettings; - - lightDirection[0] = -glm::cos(glm::radians((float)rsw->light.longitude)) * glm::sin(glm::radians((float)rsw->light.latitude)); - lightDirection[1] = glm::cos(glm::radians((float)rsw->light.latitude)); - lightDirection[2] = glm::sin(glm::radians((float)rsw->light.longitude)) * glm::sin(glm::radians((float)rsw->light.latitude)); + glm::mat4 rot = glm::mat4(1.0f); + rot = glm::rotate(rot, glm::radians(-(float)rsw->light.latitude), glm::vec3(1, 0, 0)); + rot = glm::rotate(rot, glm::radians((float)rsw->light.longitude), glm::vec3(0, 1, 0)); + lightDirection = glm::vec3(0.0f, 1.0f, 0.0f) * glm::mat3(rot); //setProgressText("Calculating map quads"); //mapQuads = gnd->getMapQuads(); @@ -196,48 +239,82 @@ void Lightmapper::run() })); } - - - - for (auto& t : threads) t.join(); - } -bool Lightmapper::collidesMap(const math::Ray& ray, float maxDistance) +bool Lightmapper::collidesMap(const math::Ray& ray, int cx, int cy, float maxDistance) { - auto point = gnd->rayCast(ray, false, 0, 0, -1, -1, 0.05f); + auto point = gnd->rayCastLightmap(ray, cx, cy, 0, 0, -1, -1, 0.05f); return point != glm::vec3(std::numeric_limits().max()) && glm::distance(point, ray.origin) < maxDistance; }; +float inverse_rsqrt(float number) +{ + const float threehalfs = 1.5F; + + float x2 = number * 0.5F; + float y = number; + + long i = *(long*)&y; -std::pair Lightmapper::calculateLight(const glm::vec3& groundPos, const glm::vec3& normal) + i = 0x5f3759df - (i >> 1); + y = *(float*)&i; + + // 1st iteration + y = y * (threehalfs - (x2 * y * y)); + return y; +} + +glm::vec3 normalize_fast(glm::vec3 v) { + float s = inverse_rsqrt(v.x * v.x + v.y * v.y + v.z * v.z); + v.x *= s; + v.y *= s; + v.z *= s; + return v; +} + +std::pair Lightmapper::calculateLight(const glm::vec3& groundPos, const glm::vec3& normal, int cx, int cy) { - int intensity = 0; + // By default, a light has 255 intensity and 0,0,0 color. + // An intensity of 255 means no shadow. + // A shadowStrength of 1 means the shadow is has an intensity of the lightmapAmbient. + // The strength of a shadow cannot be above the lightmapAmbient value. + int intensity = 255; glm::vec3 colorInc(0.0f); auto rsw = map->rootNode->getComponent(); auto& settings = rsw->lightmapSettings; - if (rsw->light.lightmapAmbient > 0) - intensity = (int)(rsw->light.lightmapAmbient * 255); + if (settings.additiveShadow) { + intensity = 0; + + if (rsw->light.lightmapAmbient > 0) + intensity = (int)(rsw->light.lightmapAmbient * 255); + } for (auto light : lights) { - auto rswObject = light->getComponent(); - auto rswLight = light->getComponent(); + auto rswObject = light.rswObject; + auto rswLight = light.rswLight; if (!rswLight->enabled) continue; glm::vec3 lightPosition(5 * gnd->width + rswObject->position.x, -rswObject->position.y, 5 * gnd->height - rswObject->position.z+10); - glm::vec3 lightDirection2 = glm::normalize(lightPosition - groundPos); + //glm::vec3 lightDirection2 = glm::normalize(lightPosition - groundPos); + glm::vec3 lightDirection2 = normalize_fast(lightPosition - groundPos); if (rswLight->lightType == RswLight::Type::Sun && !rswLight->sunMatchRswDirection) lightDirection2 = rswLight->direction; //TODO: should this be -direction? else if (rswLight->lightType == RswLight::Type::Sun && rswLight->sunMatchRswDirection) lightDirection2 = lightDirection; auto dotproduct = glm::dot(normal, lightDirection2); - if (dotproduct <= 0) + if (dotproduct <= 0) { + if (rswLight->lightType == RswLight::Type::Sun) { + if (!settings.additiveShadow && rswLight->affectShadowMap) + intensity -= (int)(255.0f * rswLight->intensity); + } + continue; + } float distance = glm::distance(lightPosition, groundPos); float attenuation = 0; @@ -253,20 +330,32 @@ std::pair Lightmapper::calculateLight(const glm::vec3& groundPos attenuation = rswLight->intensity / (denom * denom); if (rswLight->cutOff > 0) attenuation = glm::max(0.0f, (attenuation - rswLight->cutOff) / (1 - rswLight->cutOff)); + attenuation *= 255.0f; } else { if (distance > rswLight->range) continue; + float d = distance / rswLight->range; - if (rswLight->falloffStyle == RswLight::FalloffStyle::SplineTweak) - attenuation = glm::clamp(util::interpolateSpline(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; - else if (rswLight->falloffStyle == RswLight::FalloffStyle::LagrangeTweak) - attenuation = glm::clamp(util::interpolateLagrange(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; - else if (rswLight->falloffStyle == RswLight::FalloffStyle::LinearTweak) - attenuation = glm::clamp(util::interpolateLinear(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; - else if (rswLight->falloffStyle == RswLight::FalloffStyle::Exponential) - attenuation = glm::clamp((1 - glm::pow(d, rswLight->cutOff)), 0.0f, 1.0f) * 255.0f; + + switch (rswLight->falloffStyle) { + case RswLight::FalloffStyle::SplineTweak: + attenuation = glm::clamp(util::interpolateSpline(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; + break; + case RswLight::FalloffStyle::LagrangeTweak: + attenuation = glm::clamp(util::interpolateLagrange(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; + break; + case RswLight::FalloffStyle::LinearTweak: + attenuation = glm::clamp(util::interpolateLinear(rswLight->falloff, d), 0.0f, 1.0f) * 255.0f; + break; + case RswLight::FalloffStyle::S_Curve: + attenuation = glm::clamp(util::interpolateSCurve(d), 0.0f, 2.0f) * 255.0f; + break; + case RswLight::FalloffStyle::Exponential: + attenuation = glm::clamp((1 - glm::pow(d, rswLight->cutOff)), 0.0f, 1.0f) * 255.0f; + break; + } } if (rswLight->lightType == RswLight::Type::Spot) { @@ -287,24 +376,62 @@ std::pair Lightmapper::calculateLight(const glm::vec3& groundPos bool collides = false; float shadowStrength = 0.0f; + if (settings.shadows) { math::Ray ray(groundPos, lightDirection2); if (rswLight->givesShadow && attenuation > 0) { - for(auto& n : models) { + // Find all models that are on the path of the ray, using the quadtree + int qx = (int)(ray.origin.x / 10.0f); + int qy = (int)(ray.origin.z / 10.0f); + + glm::ivec2 dir(ray.dir.x < 0 ? -1 : (ray.dir.x > 0 ? 1 : 0), ray.dir.z < 0 ? -1 : (ray.dir.z > 0 ? 1 : 0)); + glm::ivec2 dirs[3] = { glm::ivec2(dir.x, 0), glm::ivec2(0, dir.y), glm::ivec2(dir.x, dir.y) }; + + std::set quadtree_models; + + while (qx >= 0 && qx < quadtree.size() && qy >= 0 && qy < quadtree[0].size()) { + for (int i = 0; i < quadtree[qx][qy].models.size(); i++) + quadtree_models.insert(&quadtree[qx][qy].models[i]); + + int j = 0; + + for (; j < 3; j++) { + int qqx = qx + dirs[j].x; + int qqy = qy + dirs[j].y; + + if ((qqx == qx && qqy == qy) || qqx < 0 || qqx >= quadtree.size() || qqy < 0 || qqy >= quadtree[0].size()) + continue; + + math::AABB box(glm::vec3(quadtree[qqx][qqy].range[0].x, -999999, quadtree[qqx][qqy].range[0].y), glm::vec3(quadtree[qqx][qqy].range[1].x, 999999, quadtree[qqx][qqy].range[1].y)); + + if (!box.hasRayCollision(ray, -999999, 9999999)) + continue; + + qx = qqx; + qy = qqy; + break; + } + + if (j == 3) + break; + } + + // Check if the ray collides with the model + for(auto n : quadtree_models) { if (collides && shadowStrength >= 1) continue; - auto rswModel = n->getComponent(); - auto collider = n->getComponent(); - if (collider->collidesTexture(ray, 0, distance- rswLight->minShadowDistance)) + + if (n->collider->collidesTexture(ray, 0, distance - rswLight->minShadowDistance)) { collides = true; - shadowStrength += rswModel->shadowStrength; + shadowStrength += n->rswModel->shadowStrength; } } } - if (!collides && shadowStrength < 1 && rswLight->shadowTerrain && rswLight->givesShadow && collidesMap(math::Ray(groundPos, lightDirection2), distance)) + // Check if the ray collides with the ground + if (!collides && shadowStrength < 1 && rswLight->shadowTerrain && rswLight->givesShadow && collidesMap(math::Ray(groundPos, lightDirection2), cx, cy, distance)) { collides = true; shadowStrength = 1; @@ -312,16 +439,16 @@ std::pair Lightmapper::calculateLight(const glm::vec3& groundPos } if (shadowStrength > 1) shadowStrength = 1; - if (shadowStrength <= 1) - { - if (rswLight->diffuseLighting) - attenuation *= dotproduct; - - if (rswLight->affectShadowMap) - intensity += (int)((1-shadowStrength) * attenuation * rswLight->intensity); - if (rswLight->affectLightmap) - colorInc += (1-shadowStrength) * (attenuation / 255.0f) * rswLight->color * rswLight->intensity; + if (rswLight->diffuseLighting) + attenuation *= dotproduct; + if (rswLight->affectShadowMap) { + if (settings.additiveShadow) + intensity += (int)((1 - shadowStrength) * attenuation * rswLight->intensity); + else + intensity -= (int)(shadowStrength * attenuation * rswLight->intensity); } + if (rswLight->affectLightmap) + colorInc += (1-shadowStrength) * (attenuation / 255.0f) * rswLight->color * rswLight->intensity; } return std::pair(colorInc, intensity); }; @@ -448,7 +575,7 @@ void Lightmapper::calcPos(int direction, int tileId, int x, int y) debugPointMutex.unlock(); } - auto light = calculateLight(groundPos, normal); + auto light = calculateLight(groundPos, normal, x, y); totalIntensity += glm::min(255, light.second); totalColor += glm::min(glm::vec3(1.0f, 1.0f, 1.0f), light.first); count++; @@ -479,6 +606,8 @@ void Lightmapper::onDone() gnd->makeLightmapBorders(browEdit); gnd->cleanLightmaps(); gnd->cleanTiles(); + lightmapChangeAction->setCurrentData(gnd->lightmaps, gnd->tiles); + map->doAction(lightmapChangeAction, browEdit); map->rootNode->getComponent()->gndShadowDirty = true; map->rootNode->getComponent()->setChunksDirty(); util::ResourceManager::clear(); diff --git a/browedit/Lightmapper.h b/browedit/Lightmapper.h index 434fbbb3..914e01e7 100644 --- a/browedit/Lightmapper.h +++ b/browedit/Lightmapper.h @@ -10,14 +10,40 @@ class BrowEdit; class Gnd; class Rsw; class Node; +class RswModel; +class RswModelCollider; +class RswObject; +class RswLight; namespace math { class Ray; } class Lightmapper { +public: + // For faster lookup, since using GetComponent gets slow if there are many models + struct light_model { + Node* node; + RswModel* rswModel; + RswModelCollider* collider; + }; + + struct light_quad_node { + std::vector models; + glm::vec2 range[2]; + }; + + // For faster lookup, since using GetComponent gets slow if there are many lights + struct light_data { + Node* light; + RswObject* rswObject; + RswLight* rswLight; + }; + +private: BrowEdit* browEdit; Gnd* gnd; Rsw* rsw; - std::vector lights; - std::vector models; + std::vector> quadtree; + std::vector lights; + std::vector models; glm::vec3 lightDirection; std::thread mainThread; @@ -32,8 +58,8 @@ class Lightmapper void run(); void onDone(); - bool collidesMap(const math::Ray& ray, float maxDistance); - std::pair calculateLight(const glm::vec3& groundPos, const glm::vec3& normal); + bool collidesMap(const math::Ray& ray, int cx, int cy, float maxDistance); + std::pair calculateLight(const glm::vec3& groundPos, const glm::vec3& normal, int cx, int cy); void calcPos(int direction, int tileId, int x, int y); void setProgressText(const std::string& text); diff --git a/browedit/Map.cpp b/browedit/Map.cpp index 70376f7a..0ccf556b 100644 --- a/browedit/Map.cpp +++ b/browedit/Map.cpp @@ -455,7 +455,7 @@ void Map::pasteSelection(BrowEdit* browEdit) browEdit->newNodesCenter = center; for (auto& n : browEdit->newNodes) n.second = n.second - center; - browEdit->newNodeHeight = false; + browEdit->newNodePlacement = BrowEdit::Ground; } } catch (...) { diff --git a/browedit/MapView.Objectmode.cpp b/browedit/MapView.Objectmode.cpp index 3ee2bc52..fe0e4702 100644 --- a/browedit/MapView.Objectmode.cpp +++ b/browedit/MapView.Objectmode.cpp @@ -237,30 +237,32 @@ void MapView::postRenderObjectMode(BrowEdit* browEdit) lockedGizmo = !ImGui::GetIO().KeyCtrl; } - if (map->selectedNodes[0]->getComponent()) { + auto rotVector = map->selectedNodes[0]->getComponent() ? &map->selectedNodes[0]->getComponent()->rotation : nullptr; + + if (rotVector) { if (!lockedGizmo) { if (map->selectedNodes.size() == 1 && gadget.mode == Gadget::Mode::Rotate) { - mat = glm::rotate(mat, -glm::radians(map->selectedNodes[0]->getComponent()->rotation.z), glm::vec3(0, 0, 1)); - rotMat = glm::rotate(rotMat, -glm::radians(map->selectedNodes[0]->getComponent()->rotation.z), glm::vec3(0, 0, 1)); + mat = glm::rotate(mat, -glm::radians(rotVector->z), glm::vec3(0, 0, 1)); + rotMat = glm::rotate(rotMat, -glm::radians(rotVector->z), glm::vec3(0, 0, 1)); } } if (map->selectedNodes.size() == 1 && gadget.mode == Gadget::Mode::Scale) { - mat = glm::rotate(mat, -glm::radians(map->selectedNodes[0]->getComponent()->rotation.z), glm::vec3(0, 0, 1)); - mat = glm::rotate(mat, -glm::radians(map->selectedNodes[0]->getComponent()->rotation.x), glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, glm::radians(map->selectedNodes[0]->getComponent()->rotation.y), glm::vec3(0, 1, 0)); + mat = glm::rotate(mat, -glm::radians(rotVector->z), glm::vec3(0, 0, 1)); + mat = glm::rotate(mat, -glm::radians(rotVector->x), glm::vec3(1, 0, 0)); + mat = glm::rotate(mat, glm::radians(rotVector->y), glm::vec3(0, 1, 0)); - rotMat = glm::rotate(rotMat, -glm::radians(map->selectedNodes[0]->getComponent()->rotation.z), glm::vec3(0, 0, 1)); - rotMat = glm::rotate(rotMat, glm::radians(map->selectedNodes[0]->getComponent()->rotation.x), glm::vec3(1, 0, 0)); - rotMat = glm::rotate(rotMat, glm::radians(map->selectedNodes[0]->getComponent()->rotation.y), glm::vec3(0, 1, 0)); + rotMat = glm::rotate(rotMat, -glm::radians(rotVector->z), glm::vec3(0, 0, 1)); + rotMat = glm::rotate(rotMat, glm::radians(rotVector->x), glm::vec3(1, 0, 0)); + rotMat = glm::rotate(rotMat, glm::radians(rotVector->y), glm::vec3(0, 1, 0)); } } - gadget.draw(mouseRay, mat, !lockedGizmo && map->selectedNodes[0]->getComponent() ? -map->selectedNodes[0]->getComponent()->rotation.x : 0); + gadget.draw(mouseRay, mat, !lockedGizmo && map->selectedNodes[0]->getComponent() ? -rotVector->x : 0); if (!lockedGizmo && map->selectedNodes[0]->getComponent() && map->selectedNodes.size() == 1 && gadget.mode == Gadget::Mode::Rotate) { if (gadget.selectedAxis == Gadget::Axis::Y) { - rotMat = glm::rotate(rotMat, glm::radians(map->selectedNodes[0]->getComponent()->rotation.x), glm::vec3(1, 0, 0)); + rotMat = glm::rotate(rotMat, glm::radians(rotVector->x), glm::vec3(1, 0, 0)); } } @@ -676,6 +678,7 @@ void MapView::postRenderObjectMode(BrowEdit* browEdit) { static bool justPressed = false; static glm::vec3 originalPosition; + static float originalHeightFromGround; if (ImGui::IsMouseClicked(0)) { auto collisions = map->rootNode->getCollisions(mouseRay); @@ -709,6 +712,20 @@ void MapView::postRenderObjectMode(BrowEdit* browEdit) { originalPosition = map->selectedNodes[0]->getComponent()->position; justPressed = true; + + if (map->selectedNodes[0]->getComponent()) { + glm::vec3 wPosition(gnd->width * 5.0f + originalPosition.x, -originalPosition.y, gnd->height * 5.0f - originalPosition.z + 10.0f); + + math::Ray ray(wPosition, glm::vec3(0, -1, 0)); + auto rayCast = gnd->rayCast(ray, viewEmptyTiles); + + if (rayCast != glm::vec3(std::numeric_limits().max())) { + originalHeightFromGround = rayCast.y - wPosition.y; + } + else { + originalHeightFromGround = 0; + } + } } } @@ -726,7 +743,11 @@ void MapView::postRenderObjectMode(BrowEdit* browEdit) auto rswObject = map->selectedNodes[0]->getComponent(); if (rswObject) { - rswObject->position = glm::vec3(rayCast.x - 5 * gnd->width, -rayCast.y, -(rayCast.z + (-10 - 5 * gnd->height))); + if (map->selectedNodes.size() == 1 && map->selectedNodes[0]->getComponent()) + rswObject->position = glm::vec3(rayCast.x - 5 * gnd->width, -rayCast.y + originalHeightFromGround, -(rayCast.z + (-10 - 5 * gnd->height))); + else + rswObject->position = glm::vec3(rayCast.x - 5 * gnd->width, -rayCast.y, -(rayCast.z + (-10 - 5 * gnd->height))); + bool snap = snapToGrid; if (ImGui::GetIO().KeyShift) snap = !snap; diff --git a/browedit/MapView.cpp b/browedit/MapView.cpp index 79699080..985222df 100644 --- a/browedit/MapView.cpp +++ b/browedit/MapView.cpp @@ -503,9 +503,14 @@ void MapView::render(BrowEdit* browEdit) } rswObject->position = glm::vec3(rayCast.x - 5 * gnd->width, -rayCast.y, -(rayCast.z + (-10 - 5 * gnd->height))) + newNode.second; - if (browEdit->newNodeHeight) - { - rswObject->position.y = newNode.second.y + browEdit->newNodesCenter.y; + + switch (browEdit->newNodePlacement) { + case BrowEdit::Relative: + rswObject->position.y += newNode.second.y + browEdit->newNodesCenter.y; + break; + case BrowEdit::Absolute: + rswObject->position.y = newNode.second.y + browEdit->newNodesCenter.y; + break; } if (newNode.first->getComponent()) @@ -919,7 +924,7 @@ void MapView::drawLight(Node* n) simpleShader->setUniform(SimpleShader::Uniforms::viewMatrix, nodeRenderContext.viewMatrix); simpleShader->setUniform(SimpleShader::Uniforms::modelMatrix, mm); simpleShader->setUniform(SimpleShader::Uniforms::textureFac, 0.0f); - simpleShader->setUniform(SimpleShader::Uniforms::color, glm::vec4(rswLight->color, 0.05f)); + simpleShader->setUniform(SimpleShader::Uniforms::color, glm::vec4(rswLight->color, 0.10f)); glDepthMask(0); glEnable(GL_BLEND); @@ -929,13 +934,22 @@ void MapView::drawLight(Node* n) float height = rswLight->range; float width = rswLight->range * tan(glm::acos(1-rswLight->spotlightWidth)); - for (float f = 0; f < 2 * glm::pi(); f += inc) { verts.push_back(VertexP3T2N3(glm::vec3(0, 0, 0), glm::vec2(0), glm::vec3(1.0f))); verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f), width * glm::cos(f)), glm::vec2(0), glm::vec3(1.0f))); verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f+inc), width * glm::cos(f+inc)), glm::vec2(0), glm::vec3(1.0f))); } + + if (showLightSphere) { + for (float f = 0; f < 2 * glm::pi(); f += inc) + { + verts.push_back(VertexP3T2N3(glm::vec3(height, 0, 0), glm::vec2(0), glm::vec3(1.0f))); + verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f), width * glm::cos(f)), glm::vec2(0), glm::vec3(1.0f))); + verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f + inc), width * glm::cos(f + inc)), glm::vec2(0), glm::vec3(1.0f))); + } + } + glBindBuffer(GL_ARRAY_BUFFER, 0); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); @@ -948,7 +962,30 @@ void MapView::drawLight(Node* n) glDrawArrays(GL_TRIANGLES, 0, (int)verts.size()); + if (!showLightSphere) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glLineWidth(1); + simpleShader->setUniform(SimpleShader::Uniforms::color, glm::vec4(rswLight->color, 0.8f)); + glDrawArrays(GL_TRIANGLES, 0, (int)verts.size()); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + glDepthMask(1); + + if (!showLightSphere) { + simpleShader->setUniform(SimpleShader::Uniforms::color, glm::vec4(rswLight->color, 1.0f)); + verts.clear(); + + for (float f = 0; f < 2 * glm::pi(); f += inc) + { + verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f), width * glm::cos(f)), glm::vec2(0), glm::vec3(1.0f))); + verts.push_back(VertexP3T2N3(glm::vec3(height, width * glm::sin(f + inc), width * glm::cos(f + inc)), glm::vec2(0), glm::vec3(1.0f))); + } + + glLineWidth(2); + glDrawArrays(GL_LINES, 0, (int)verts.size()); + } + glUseProgram(0); } } diff --git a/browedit/MapView.h b/browedit/MapView.h index e94448e7..4a9980d5 100644 --- a/browedit/MapView.h +++ b/browedit/MapView.h @@ -109,7 +109,7 @@ class MapView float gadgetOpacity = 0.5f; float gadgetScale = 1.0f; - float gadgetThickness = 1.0f; + float gadgetThickness = 2.0f; int quadTreeMaxLevel = 0; @@ -206,7 +206,7 @@ class MapView bool viewLightmapColor = true; bool viewColors = true; bool viewLighting = true; - bool smoothColors = false; + bool smoothColors = true; bool viewTextures = true; bool viewEmptyTiles = true; bool viewGat = false; @@ -221,6 +221,8 @@ class MapView bool viewWater = true; bool viewEffectIcons = true; + bool enableLightQuickPreview = false; + void focusSelection(); void drawLight(Node* n); }; \ No newline at end of file diff --git a/browedit/actions/LightmapChangeAction.cpp b/browedit/actions/LightmapChangeAction.cpp new file mode 100644 index 00000000..d247e77c --- /dev/null +++ b/browedit/actions/LightmapChangeAction.cpp @@ -0,0 +1,78 @@ +#include "LightmapChangeAction.h" + +#include +#include +#include +#include + +LightmapChangeAction::LightmapChangeAction() +{ +} + +LightmapChangeAction::~LightmapChangeAction() +{ + for (int i = 0; i < m_oldLightmaps.size(); i++) + delete m_oldLightmaps[i]; + + for (int i = 0; i < m_newLightmaps.size(); i++) + delete m_newLightmaps[i]; +} + +void LightmapChangeAction::setPreviousData(std::vector lightmaps, std::vector tiles) +{ + for (int i = 0; i < lightmaps.size(); i++) + m_oldLightmaps.push_back(new Gnd::Lightmap(*lightmaps[i])); + for (int i = 0; i < tiles.size(); i++) + m_oldLightmapIndex.push_back(tiles[i]->lightmapIndex); +} + +void LightmapChangeAction::setCurrentData(std::vector lightmaps, std::vector tiles) +{ + for (int i = 0; i < lightmaps.size(); i++) + m_newLightmaps.push_back(new Gnd::Lightmap(*lightmaps[i])); + for (int i = 0; i < tiles.size(); i++) + m_newLightmapIndex.push_back(tiles[i]->lightmapIndex); +} + +void LightmapChangeAction::perform(Map* map, BrowEdit* browEdit) +{ + Gnd* gnd = map->rootNode->getComponent(); + + for (int i = 0; i < gnd->lightmaps.size(); i++) + delete gnd->lightmaps[i]; + + gnd->lightmaps.clear(); + + for (int i = 0; i < m_newLightmaps.size(); i++) + gnd->lightmaps.push_back(new Gnd::Lightmap(*m_newLightmaps[i])); + + for (int i = 0; i < m_newLightmapIndex.size(); i++) + gnd->tiles[i]->lightmapIndex = m_newLightmapIndex[i]; + + map->rootNode->getComponent()->gndShadowDirty = true; + map->rootNode->getComponent()->setChunksDirty(); +} + +void LightmapChangeAction::undo(Map* map, BrowEdit* browEdit) +{ + Gnd* gnd = map->rootNode->getComponent(); + + for (int i = 0; i < gnd->lightmaps.size(); i++) + delete gnd->lightmaps[i]; + + gnd->lightmaps.clear(); + + for (int i = 0; i < m_oldLightmaps.size(); i++) + gnd->lightmaps.push_back(new Gnd::Lightmap(*m_oldLightmaps[i])); + + for (int i = 0; i < m_oldLightmapIndex.size(); i++) + gnd->tiles[i]->lightmapIndex = m_oldLightmapIndex[i]; + + map->rootNode->getComponent()->gndShadowDirty = true; + map->rootNode->getComponent()->setChunksDirty(); +} + +std::string LightmapChangeAction::str() +{ + return "Changed Lightmap"; +} diff --git a/browedit/actions/LightmapChangeAction.h b/browedit/actions/LightmapChangeAction.h new file mode 100644 index 00000000..5429f38c --- /dev/null +++ b/browedit/actions/LightmapChangeAction.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Action.h" +#include + +class LightmapChangeAction : public Action +{ + std::vector m_oldLightmaps; + std::vector m_newLightmaps; + std::vector m_oldLightmapIndex; + std::vector m_newLightmapIndex; +public: + LightmapChangeAction(); + ~LightmapChangeAction(); + + void setPreviousData(std::vector lightmaps, std::vector tiles); + void setCurrentData(std::vector lightmaps, std::vector tiles); + + virtual void perform(Map* map, BrowEdit* browEdit) override; + virtual void undo(Map* map, BrowEdit* browEdit) override; + + virtual std::string str() override; +}; \ No newline at end of file diff --git a/browedit/components/Gnd.cpp b/browedit/components/Gnd.cpp index a5acfe0c..f13ecb07 100644 --- a/browedit/components/Gnd.cpp +++ b/browedit/components/Gnd.cpp @@ -489,7 +489,181 @@ void Gnd::save(const std::string& fileName, Rsw *rsw) std::cout << "GND: Done saving GND" << std::endl; } +glm::vec3 Gnd::rayCastLightmap(const math::Ray& ray, int cx, int cy, int xMin, int yMin, int xMax, int yMax, float rayOffset) +{ + if (cubes.size() == 0) + return glm::vec3(std::numeric_limits::max()); + + if (xMax == -1) + xMax = (int)cubes.size(); + if (yMax == -1) + yMax = (int)cubes[0].size(); + + xMin = glm::max(0, xMin); + yMin = glm::max(0, yMin); + xMax = glm::min(xMax, (int)cubes.size()); + yMax = glm::min(yMax, (int)cubes[0].size()); + + float f = 0; + + // Tokei: + // Some explanation for this... + // Instead of testing the collision from the ray against every gnd cube, the algorithm below + // draws a "2d" line, on the xz coordinate system. The line is the ray we're testing, and we simply + // move along the line until we reach the next gnd cube. + // + // For additional optimization, we also check for the Y position every time we hit a new cube. + // If the Y position is greater than any of the vertex of the cube (+ walls), then we skip the cube. + // The Y position is kept for the next cube check to avoid recalculating the values. This only works + // if the Y position goes up on the axis, which is (theoretically) always the case. It is very useful + // since maps tend to have many flat cubes and we can skip the majority of them. + + // line function: y = xz_a * x + xz_b; + float xz_a = ray.dir.x == 0 ? 1 : ray.dir.z / ray.dir.x; + float xz_b = ray.origin.z - xz_a * ray.origin.x; + float xz_ray_length = glm::length(glm::vec2(ray.dir.x, ray.dir.z)); + float y = 9999999999.0f; + + glm::ivec2 dir(ray.dir.x < 0 ? -1 : (ray.dir.x > 0 ? 1 : 0), ray.dir.z < 0 ? 1 : (ray.dir.z > 0 ? -1 : 0)); + + // Cannot collide with another cube directly above itself. + if (dir[0] == 0 && dir[1] == 0) + return glm::vec3(std::numeric_limits::max()); + + // To test collision against the original cube; a tad annoying to handle + bool first = true; + int cxx, cyy, next_cx = 0; + + while (cx >= xMin && cx < xMax && cy >= yMin && cy < yMax) { + if (first) { + cxx = cx; + cyy = cy; + } + else { + // Z axis only + if (ray.dir.x == 0) { + next_cx = cx; + } + else { + float next_x; + + if (ray.dir.z > 0) + next_x = ((height - cy + 1) * 10.0f - xz_b) / xz_a; + else + next_x = ((height - cy) * 10.0f - xz_b) / xz_a; + + next_cx = (int)(next_x / 10); + } + + // If we don't move along the X axis, then move along the Z axis. + // cxx/cyy is the next cube to check for a collision. + cxx = cx + (next_cx != cx ? dir[0] : 0); + cyy = cy + (next_cx == cx ? dir[1] : 0); + + if ((cxx == cx && cyy == cy) || cxx < xMin || cxx >= xMax || cyy < yMin || cyy >= yMax) + break; + } + + Gnd::Cube* cube = cubes[cxx][cyy]; + + if ((cube->tileUp != -1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h1, glm::min(cube->h2, glm::min(cube->h3, cube->h4)))))) + || (cube->tileSide != -1 && cxx < width - 1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h2, glm::min(cube->h4, glm::min(cubes[cxx + 1][cyy]->h1, cubes[cxx + 1][cyy]->h3)))))) + || (cube->tileFront != -1 && cyy < height - 1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h3, glm::min(cube->h4, glm::min(cubes[cxx][cyy + 1]->h2, cubes[cxx][cyy + 1]->h1)))))) + ) { + float xx, zz; + + if (!first) { + if (next_cx == cx) { + zz = (height - cy + 1 + ((dir[1] + 1) >> 1)) * 10.0f; + xx = (zz - xz_b) / xz_a; + } + else { + xx = (cx + ((dir[0] + 1) >> 1)) * 10.0f; + zz = xz_a * xx + xz_b; + } + + y = -ray.dir.y * (glm::length(glm::vec2(xx, zz) - glm::vec2(ray.origin.x, ray.origin.z)) / xz_ray_length) - ray.origin.y; + } + else { + y = -ray.origin.y; + } + } + else { + // y value went above the next cube vertices, skip + cx = cxx; + cy = cyy; + first = false; + continue; + } + + cx = cxx; + cy = cyy; + first = false; + + if (cube->tileUp != -1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h1, glm::min(cube->h2, glm::min(cube->h3, cube->h4)))))) + { + glm::vec3 v1(10 * cx, -cube->h3, 10 * height - 10 * cy); + glm::vec3 v2(10 * cx + 10, -cube->h4, 10 * height - 10 * cy); + glm::vec3 v3(10 * cx, -cube->h1, 10 * height - 10 * cy + 10); + glm::vec3 v4(10 * cx + 10, -cube->h2, 10 * height - 10 * cy + 10); + + { + std::vector v{ v4, v2, v1 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + { + std::vector v{ v4, v1, v3 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + } + if (cube->tileSide != -1 && cx < width - 1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h2, glm::min(cube->h4, glm::min(cubes[cx + 1][cy]->h1, cubes[cx + 1][cy]->h3)))))) + { + glm::vec3 v1(10 * cx + 10, -cube->h2, 10 * height - 10 * cy + 10); + glm::vec3 v2(10 * cx + 10, -cube->h4, 10 * height - 10 * cy); + glm::vec3 v3(10 * cx + 10, -cubes[cx + 1][cy]->h1, 10 * height - 10 * cy + 10); + glm::vec3 v4(10 * cx + 10, -cubes[cx + 1][cy]->h3, 10 * height - 10 * cy); + + { + std::vector v{ v4, v2, v1 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + { + std::vector v{ v4, v1, v3 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + } + if (cube->tileFront != -1 && cy < height - 1 && (ray.dir.y <= 0 || (y >= glm::min(cube->h3, glm::min(cube->h4, glm::min(cubes[cx][cy + 1]->h2, cubes[cx][cy + 1]->h1)))))) + { + glm::vec3 v1(10 * cx, -cube->h3, 10 * height - 10 * cy); + glm::vec3 v2(10 * cx + 10, -cube->h4, 10 * height - 10 * cy); + glm::vec3 v4(10 * cx + 10, -cubes[cx][cy + 1]->h2, 10 * height - 10 * cy); + glm::vec3 v3(10 * cx, -cubes[cx][cy + 1]->h1, 10 * height - 10 * cy); + + { + std::vector v{ v4, v2, v1 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + { + std::vector v{ v4, v1, v3 }; + if (ray.LineIntersectPolygon(v, f)) + if (f >= rayOffset) + return ray.origin + f * ray.dir; + } + } + } + return glm::vec3(std::numeric_limits::max()); +} glm::vec3 Gnd::rayCast(const math::Ray& ray, bool emptyTiles, int xMin, int yMin, int xMax, int yMax, float rayOffset) { @@ -603,7 +777,7 @@ glm::vec3 Gnd::rayCast(const math::Ray& ray, bool emptyTiles, int xMin, int yMin void Gnd::makeLightmapsUnique() { makeTilesUnique(); - cleanLightmaps(); + cleanLightmaps(); std::set taken; for (Tile* t : tiles) { @@ -927,49 +1101,52 @@ void Gnd::makeLightmapBorders(BrowEdit* browEdit) node->getComponent()->gndShadowDirty = true; } - void Gnd::cleanLightmaps() { - std::cout<< "Lightmap cleanup, starting with " << lightmaps.size() << " lightmaps" <> lookup; - + std::cout << "Lightmap cleanup, starting with " << lightmaps.size() << " lightmaps" << std::endl; + + std::map> light2lightmapIndex; + std::map oldIndex2newIndex; + std::vector newLightmaps; + for (int i = 0; i < (int)lightmaps.size(); i++) { - unsigned char hash = lightmaps[i]->hash(); + unsigned int hash = lightmaps[i]->hash(); bool found = false; - if (lookup.find(hash) != lookup.end()) - { - for (const auto ii : lookup[hash]) - { - if ((*lightmaps[i]) == (*lightmaps[ii])) - {// if it is found - assert(i > (int)ii); - //change all tiles with lightmap i to ii - for (auto tile : tiles) - if (tile->lightmapIndex == i) - tile->lightmapIndex = (unsigned short)ii; - else if (tile->lightmapIndex > i) - tile->lightmapIndex--; - //remove lightmap i - delete lightmaps[i]; - lightmaps.erase(lightmaps.begin() + i); - i--; + + if (light2lightmapIndex.find(hash) != light2lightmapIndex.end()) { + for (const auto ii : light2lightmapIndex[hash]) { + if ((*lightmaps[i]) == (*lightmaps[ii])) { + oldIndex2newIndex[i] = oldIndex2newIndex[ii]; found = true; break; } } } - if (!found) - { - lookup[hash].push_back(i); + + if (!found) { + light2lightmapIndex[hash].push_back(i); + oldIndex2newIndex[i] = (unsigned short)newLightmaps.size(); + newLightmaps.push_back(new Lightmap(*lightmaps[i])); } - + } + + for (int i = 0; i < (int)lightmaps.size(); i++) + delete lightmaps[i]; + + lightmaps.clear(); + + for (int i = 0; i < (int)newLightmaps.size(); i++) { + lightmaps.push_back(newLightmaps[i]); + } + + for (auto tile : tiles) { + tile->lightmapIndex = oldIndex2newIndex[tile->lightmapIndex]; } std::cout<< "Lightmap cleanup, ending with " << lightmaps.size() << " lightmaps" << std::endl; node->getComponent()->setChunksDirty(); node->getComponent()->gndShadowDirty = true; - } @@ -1367,16 +1544,30 @@ std::vector Gnd::getMapQuads() return quads; } -const unsigned char Gnd::Lightmap::hash() const //actually a crc, but will work +const unsigned int Gnd::Lightmap::hash() const { #define POLY 0x82f63b78 - unsigned char crc = ~0; - for (int i = 0; i < gnd->lightmapWidth*gnd->lightmapHeight*4; i++) { + const int precision = 8; + unsigned int crc = ~0; + int size = gnd->lightmapWidth * gnd->lightmapHeight * 4; + if (size < 4) + return 0; + for (int i = 0; i < size; i++) { crc ^= data[i]; - for (int k = 0; k < 8; k++) - crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; + crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; + //crc += (data[i] ^ POLY) & 0xff; } return ~crc; + + //const int precision = 8; + //unsigned long crc = 0; + //int size = gnd->lightmapWidth * gnd->lightmapHeight * 4; + //if (size < 4) + // return 0; + //for (int i = 0; i < precision; i++) { + // crc |= (data[i * size / precision] & 0xf) << (i * precision); + //} + //return crc; } diff --git a/browedit/components/Gnd.h b/browedit/components/Gnd.h index 4ecd8407..6fea9109 100644 --- a/browedit/components/Gnd.h +++ b/browedit/components/Gnd.h @@ -25,6 +25,7 @@ class Gnd : public Component, public ImguiProps ~Gnd(); void save(const std::string &fileName, Rsw* rsw); glm::vec3 rayCast(const math::Ray& ray, bool emptyTiles = false, int xMin = 0, int yMin = 0, int xMax = -1, int yMax = -1, float offset = 0.0f); + glm::vec3 rayCastLightmap(const math::Ray& ray, int cx, int cy, int xMin = 0, int yMin = 0, int xMax = -1, int yMax = -1, float offset = 0.0f); void makeLightmapsUnique(); void makeLightmapsClear(); void makeLightmapBorders(BrowEdit* browEdit); @@ -128,7 +129,7 @@ class Gnd : public Component, public ImguiProps unsigned char* data; Gnd* gnd; void expandBorders(); - const unsigned char hash() const; + const unsigned int hash() const; bool operator == (const Lightmap& other) const; LightmapRow operator [] (int x) { return LightmapRow{ this, x }; } friend void to_json(nlohmann::json& nlohmann_json_j, const Lightmap& nlohmann_json_t) { diff --git a/browedit/components/GndRenderer.cpp b/browedit/components/GndRenderer.cpp index a91a7fba..350a3a1f 100644 --- a/browedit/components/GndRenderer.cpp +++ b/browedit/components/GndRenderer.cpp @@ -140,6 +140,30 @@ void GndRenderer::render() //shader->setUniform(GndShader::Uniforms::fogExp, rsw->fog.factor); shader->setUniform(GndShader::Uniforms::fogColor, rsw->fog.color); + if (quickRenderLightNode != nullptr) { + auto rswLight = quickRenderLightNode->getComponent(); + auto rswObject = quickRenderLightNode->getComponent(); + + shader->setUniform(GndShader::Uniforms::light_position, glm::vec3(gnd->width * 5.0f + rswObject->position.x, -rswObject->position.y, gnd->height * 5.0f + 10.0f - rswObject->position.z)); + shader->setUniform(GndShader::Uniforms::light_color, rswLight->color); + shader->setUniform(GndShader::Uniforms::lightCount, 1); + shader->setUniform(GndShader::Uniforms::light_type, (int)rswLight->lightType); + shader->setUniform(GndShader::Uniforms::light_falloff_style, (int)rswLight->falloffStyle); + shader->setUniform(GndShader::Uniforms::light_range, rswLight->range); + shader->setUniform(GndShader::Uniforms::light_intensity, rswLight->intensity); + shader->setUniform(GndShader::Uniforms::light_cutoff, rswLight->cutOff); + shader->setUniform(GndShader::Uniforms::light_diffuseLighting, rswLight->diffuseLighting); + shader->setUniform(GndShader::Uniforms::light_direction, rswLight->direction); + shader->setUniform(GndShader::Uniforms::light_width, rswLight->spotlightWidth); + shader->setUniform(GndShader::Uniforms::light_falloff_count, glm::min(10, (int)rswLight->falloff.size())); + + for (int i = 0; i < rswLight->falloff.size() && i < 10; i++) { + shader->setUniform(GndShader::Uniforms::light_falloff_0 + i, rswLight->falloff[i]); + } + } + else { + shader->setUniform(GndShader::Uniforms::lightCount, 0); + } for (auto r : chunks) diff --git a/browedit/components/GndRenderer.h b/browedit/components/GndRenderer.h index 6c3f429b..9d9a9a39 100644 --- a/browedit/components/GndRenderer.h +++ b/browedit/components/GndRenderer.h @@ -89,6 +89,7 @@ class GndRenderer : public Renderer bool smoothColors = false; bool viewTextures = true; bool viewFog = true; + Node* quickRenderLightNode = nullptr; bool viewEmptyTiles = true; }; \ No newline at end of file diff --git a/browedit/components/Rsw.Light.cpp b/browedit/components/Rsw.Light.cpp index 97bf843d..e20e5e19 100644 --- a/browedit/components/Rsw.Light.cpp +++ b/browedit/components/Rsw.Light.cpp @@ -2,6 +2,8 @@ #include "Rsw.h" #include #include +#include +#include #include #include @@ -181,6 +183,25 @@ void RswLight::buildImGuiMulti(BrowEdit* browEdit, const std::vector& nod } util::CheckboxMulti(browEdit, browEdit->activeMapView->map, rswLights, "Enabled", [](RswLight* l) { return &l->enabled; }); + //if (ImGui::Button("Save as template")) { + if (ImGui::Checkbox("Quick preview", &browEdit->activeMapView->enableLightQuickPreview)) { + if (!browEdit->activeMapView->enableLightQuickPreview) { + auto gndRenderer = browEdit->activeMapView->map->rootNode->getComponent(); + + if (gndRenderer) + gndRenderer->quickRenderLightNode = nullptr; + } + } + + if (browEdit->activeMapView->enableLightQuickPreview) { + auto gndRenderer = browEdit->activeMapView->map->rootNode->getComponent(); + + if (gndRenderer) { + if (browEdit->activeMapView->enableLightQuickPreview) + gndRenderer->quickRenderLightNode = rswLights[0]->node; + } + } + util::ColorEdit3Multi(browEdit, browEdit->activeMapView->map, rswLights, "Color", [](RswLight* l) { return &l->color; }); if(rswLights.front()->lightType != RswLight::Type::Sun) util::DragFloatMulti(browEdit, browEdit->activeMapView->map, rswLights, "Range", [](RswLight* l) { return &l->range; }, 1.0f, 0.0f, 1000.0f); @@ -208,7 +229,7 @@ void RswLight::buildImGuiMulti(BrowEdit* browEdit, const std::vector& nod } if (rswLights.front()->lightType != RswLight::Type::Sun) { - util::ComboBoxMulti(browEdit, browEdit->activeMapView->map, rswLights, "Falloff style", "exponential\0spline tweak\0lagrange tweak\0linear tweak\0magic\0", [](RswLight* l) { return (int*) & l->falloffStyle; }); + util::ComboBoxMulti(browEdit, browEdit->activeMapView->map, rswLights, "Falloff style", "exponential\0spline tweak\0lagrange tweak\0linear tweak\0magic\0s curve\0", [](RswLight* l) { return (int*) & l->falloffStyle; }); differentValues = !std::all_of(rswLights.begin(), rswLights.end(), [&](RswLight* o) { return o->falloffStyle == rswLights.front()->falloffStyle; }); if (!differentValues) @@ -226,6 +247,8 @@ void RswLight::buildImGuiMulti(BrowEdit* browEdit, const std::vector& nod util::EditableGraphMulti(browEdit, browEdit->activeMapView->map, rswLights, "Light Falloff", [](RswLight* l) {return &l->falloff; }, util::interpolateLagrange); else if (falloffStyle == FalloffStyle::LinearTweak) util::EditableGraphMulti(browEdit, browEdit->activeMapView->map, rswLights, "Light Falloff", [](RswLight* l) {return &l->falloff; }, util::interpolateLinear); + else if (falloffStyle == FalloffStyle::S_Curve) + util::Graph("Light Falloff", [&](float x) { return util::interpolateSCurve(x) / 2.0f; }); else if (falloffStyle == FalloffStyle::Exponential) { bool differentFalloff = !std::all_of(rswLights.begin(), rswLights.end(), [&](RswLight* o) { return o->falloffStyle == rswLights.front()->falloffStyle; }); diff --git a/browedit/components/Rsw.cpp b/browedit/components/Rsw.cpp index 33ed8266..21884091 100644 --- a/browedit/components/Rsw.cpp +++ b/browedit/components/Rsw.cpp @@ -1114,6 +1114,9 @@ std::vector RswModelCollider::getCollisions(Rsm::Mesh* mesh, const ma return ret; } +double debug5_start[20]; +double debug5_stop[20]; + bool RswModelCollider::collidesTexture(const math::Ray& ray, float minDistance, float maxDistance) { std::vector ret; @@ -1124,33 +1127,91 @@ bool RswModelCollider::collidesTexture(const math::Ray& ray, float minDistance, rsm = node->getComponent(); if (!rsmRenderer) rsmRenderer = node->getComponent(); + if (!rswModel || !rsm || !rsmRenderer) return false; if (!rswModel->aabb.hasRayCollision(ray, 0, 10000000)) return false; - return collidesTexture(rsm->rootMesh, ray, rsmRenderer->matrixCache, minDistance, maxDistance); + + auto res = collidesTexture(rsm->rootMesh, ray, rsmRenderer->matrixCache, minDistance, maxDistance); + return res; +} + +// Buffers all the face coordinates to their rendered position +void RswModelCollider::calculateWorldFaces() +{ + buffered_faces.clear(); + + if (!rswModel) + rswModel = node->getComponent(); + if (!rsm) + rsm = node->getComponent(); + if (!rsmRenderer) + rsmRenderer = node->getComponent(); + + if (!rswModel || !rsm || !rsmRenderer) + return; + + calculateWorldFaces(rsm->rootMesh, rsmRenderer->matrixCache); } +void RswModelCollider::calculateWorldFaces(Rsm::Mesh* mesh, const glm::mat4& matrix) { + if (!mesh || mesh->index >= rsmRenderer->renderInfo.size()) + return; + + glm::mat4 newMatrix = matrix * rsmRenderer->renderInfo[mesh->index].matrix; + + if (buffered_faces.size() < rsmRenderer->renderInfo.size()) + buffered_faces.resize(rsmRenderer->renderInfo.size()); + + std::vector>* faces = &buffered_faces[mesh->index]; + + if (buffered_faces[mesh->index].size() == 0 && mesh->faces.size() > 0) { + (*faces).resize(mesh->faces.size()); + + for (size_t i = 0; i < mesh->faces.size(); i++) + { + for (size_t ii = 0; ii < 3; ii++) + (*faces)[i].push_back(newMatrix * glm::vec4(mesh->vertices[mesh->faces[i].vertexIds[ii]], 1.0f)); + } + } + + for (size_t i = 0; i < mesh->children.size(); i++) + calculateWorldFaces(mesh->children[i], matrix); +} bool RswModelCollider::collidesTexture(Rsm::Mesh* mesh, const math::Ray& ray, const glm::mat4& matrix, float minDistance, float maxDistance) { if (!mesh || mesh->index >= rsmRenderer->renderInfo.size()) return false; - std::vector ret; glm::mat4 newMatrix = matrix * rsmRenderer->renderInfo[mesh->index].matrix; - //math::Ray newRay(ray * glm::inverse(newMatrix)); //would rather work with the inversed ray here, but that doesn't work for scaled models - std::vector verts; - verts.resize(3); + if (buffered_faces.size() < rsmRenderer->renderInfo.size()) + buffered_faces.resize(rsmRenderer->renderInfo.size()); + + std::vector> *faces = &buffered_faces[mesh->index]; + + if (buffered_faces[mesh->index].size() == 0 && mesh->faces.size() > 0) { + (*faces).resize(mesh->faces.size()); + + for (size_t i = 0; i < mesh->faces.size(); i++) + { + for (size_t ii = 0; ii < 3; ii++) + (*faces)[i].push_back(newMatrix * glm::vec4(mesh->vertices[mesh->faces[i].vertexIds[ii]], 1.0f)); + } + } + float t; + + std::vector* verts; + for (size_t i = 0; i < mesh->faces.size(); i++) { - for (size_t ii = 0; ii < 3; ii++) - verts[ii] = newMatrix * glm::vec4(mesh->vertices[mesh->faces[i].vertexIds[ii]],1.0f); - - if (ray.LineIntersectPolygon(verts, t) && t > 0) + verts = &(*faces)[i]; + + if (ray.LineIntersectPolygon(*verts, t) && t > 0) { Image* img = nullptr; auto rsmMesh = dynamic_cast(mesh); @@ -1165,11 +1226,11 @@ bool RswModelCollider::collidesTexture(Rsm::Mesh* mesh, const math::Ray& ray, co { if (glm::distance(hitPoint, ray.origin) >= minDistance && maxDistance - t > 0) { - auto f1 = verts[0] - hitPoint; - auto f2 = verts[1] - hitPoint; - auto f3 = verts[2] - hitPoint; + auto f1 = (*verts)[0] - hitPoint; + auto f2 = (*verts)[1] - hitPoint; + auto f3 = (*verts)[2] - hitPoint; - float a = glm::length(glm::cross(verts[0] - verts[1], verts[0] - verts[2])); + float a = glm::length(glm::cross((*verts)[0] - (*verts)[1], (*verts)[0] - (*verts)[2])); float a1 = glm::length(glm::cross(f2, f3)) / a; float a2 = glm::length(glm::cross(f3, f1)) / a; float a3 = glm::length(glm::cross(f1, f2)) / a; @@ -1195,9 +1256,8 @@ bool RswModelCollider::collidesTexture(Rsm::Mesh* mesh, const math::Ray& ray, co return true; } } - else if(glm::distance(hitPoint, ray.origin) >= minDistance && maxDistance - t > 0) //remove the if condition here???? + else if (glm::distance(hitPoint, ray.origin) >= minDistance && maxDistance - t > 0) //remove the if condition here???? return true; - } } diff --git a/browedit/components/Rsw.h b/browedit/components/Rsw.h index 89db5fba..26063140 100644 --- a/browedit/components/Rsw.h +++ b/browedit/components/Rsw.h @@ -133,6 +133,7 @@ class Rsw : public Component, public ImguiProps int quality = 1; bool shadows = true; bool heightSelectionOnly = false; + bool additiveShadow = true; glm::ivec2 rangeX; glm::ivec2 rangeY; @@ -216,10 +217,11 @@ class RswLight : public Component Spot, Sun } lightType = Type::Point; - bool givesShadow = true; - bool affectShadowMap = true; + bool givesShadow = false; + bool affectShadowMap = false; bool affectLightmap = true; bool enabled = true; + bool quickPreview = true; bool shadowTerrain = true; bool sunMatchRswDirection = true; @@ -233,8 +235,9 @@ class RswLight : public Component LagrangeTweak = 2, LinearTweak = 3, Magic = 4, + S_Curve = 5, }; - FalloffStyle falloffStyle = FalloffStyle::Exponential; + FalloffStyle falloffStyle = FalloffStyle::S_Curve; float cutOff = 0.5f; float intensity = 1; @@ -249,7 +252,7 @@ class RswLight : public Component void save(std::ofstream& file); nlohmann::json saveExtra(); static void buildImGuiMulti(BrowEdit* browEdit, const std::vector&); - NLOHMANN_DEFINE_TYPE_INTRUSIVE(RswLight, color, enabled, lightType, spotlightWidth, sunMatchRswDirection, direction, range, givesShadow, cutOff, cutOff, intensity, affectShadowMap, affectLightmap, falloff, falloffStyle, shadowTerrain); + NLOHMANN_DEFINE_TYPE_INTRUSIVE(RswLight, color, enabled, lightType, spotlightWidth, sunMatchRswDirection, direction, range, givesShadow, cutOff, cutOff, intensity, minShadowDistance, affectShadowMap, affectLightmap, falloff, falloffStyle, shadowTerrain, diffuseLighting); }; class LubEffect : public Component @@ -321,6 +324,7 @@ class RswSound : public Component class RswModelCollider : public Collider { + std::vector>> buffered_faces; RswModel* rswModel = nullptr; Rsm* rsm = nullptr; RsmRenderer* rsmRenderer = nullptr; @@ -328,6 +332,8 @@ class RswModelCollider : public Collider void begin(); bool collidesTexture(const math::Ray& ray, float minDistance = 0.0f, float maxDistance = 9999999.0f); bool collidesTexture(Rsm::Mesh* mesh, const math::Ray& ray, const glm::mat4& matrix, float minDistance, float maxDistance); + void calculateWorldFaces(Rsm::Mesh* mesh, const glm::mat4& matrix); + void calculateWorldFaces(); std::vector getVerticesWorldSpace(Rsm::Mesh* mesh = nullptr, const glm::mat4& matrix = glm::mat4(1.0f)); std::vector getAllVerticesWorldSpace(Rsm::Mesh* mesh = nullptr, const glm::mat4& matrix = glm::mat4(1.0f)); diff --git a/browedit/gl/Shader.h b/browedit/gl/Shader.h index 7f9e0011..383ac7fe 100644 --- a/browedit/gl/Shader.h +++ b/browedit/gl/Shader.h @@ -9,9 +9,10 @@ namespace gl class Shader { std::string file; - GLuint programId; int* uniforms; public: + GLuint programId; + Shader(const std::string& file, int end); ~Shader(); void setUniform(int name, bool value); diff --git a/browedit/math/Ray.cpp b/browedit/math/Ray.cpp index e9bbc554..b0ee317a 100644 --- a/browedit/math/Ray.cpp +++ b/browedit/math/Ray.cpp @@ -39,35 +39,38 @@ namespace math bool Ray::LineIntersectPolygon(const std::span &vertices, float &t) const { - Plane plane; - plane.normal = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); - if (glm::length(plane.normal) < 0.000001f) + // Möller–Trumbore intersection algorithm + constexpr float epsilon = 0.001f; + + glm::vec3 edge1 = vertices[1] - vertices[0]; + glm::vec3 edge2 = vertices[2] - vertices[0]; + glm::vec3 ray_cross_e2 = glm::cross(dir, edge2); + float det = glm::dot(edge1, ray_cross_e2); + + if (det > -epsilon && det < epsilon) return false; - plane.normal = glm::normalize(plane.normal); - plane.D = -glm::dot(plane.normal, vertices[0]); - float tt; - if (!planeIntersection(plane, tt)) + float inv_det = 1.0f / det; + glm::vec3 s = origin - vertices[0]; + float u = inv_det * glm::dot(s, ray_cross_e2); + + if ((u < 0 && abs(u) > epsilon) || (u > 1 && abs(u - 1) > epsilon)) return false; - glm::vec3 intersection = origin + dir * tt; + glm::vec3 s_cross_e1 = glm::cross(s, edge1); + float v = inv_det * glm::dot(dir, s_cross_e1); - /* if (Intersection == EndLine) - return false;*/ - for (size_t vertex = 0; vertex < vertices.size(); vertex++) - { - Plane edgePlane; - int NextVertex = (vertex + 1) % (int)vertices.size(); - glm::vec3 EdgeVector = vertices[NextVertex] - vertices[vertex]; - edgePlane.normal = glm::normalize(glm::cross(EdgeVector, plane.normal)); - edgePlane.D = -glm::dot(edgePlane.normal, vertices[vertex]); - - if (glm::dot(edgePlane.normal, intersection) + edgePlane.D > 0.001f) - return false; + if ((v < 0 && abs(v) > epsilon) || (u + v > 1 && abs(u + v - 1) > epsilon)) + return false; + + float tt = inv_det * glm::dot(edge2, s_cross_e1); + + if (tt > epsilon) { + t = tt; + return true; } - t = tt; - return true; + return false; } diff --git a/browedit/shaders/GndShader.h b/browedit/shaders/GndShader.h index e3719621..6b8befa9 100644 --- a/browedit/shaders/GndShader.h +++ b/browedit/shaders/GndShader.h @@ -27,6 +27,28 @@ class GndShader : public gl::Shader fogNear, fogFar, //fogExp, + lightCount, + light_position, + light_color, + light_type, + light_falloff_style, + light_range, + light_intensity, + light_cutoff, + light_diffuseLighting, + light_direction, + light_width, + light_falloff_0, + light_falloff_1, + light_falloff_2, + light_falloff_3, + light_falloff_4, + light_falloff_5, + light_falloff_6, + light_falloff_7, + light_falloff_8, + light_falloff_9, + light_falloff_count, End }; }; @@ -50,5 +72,27 @@ class GndShader : public gl::Shader bindUniform(Uniforms::fogNear, "fogNear"); bindUniform(Uniforms::fogFar, "fogFar"); //bindUniform(Uniforms::fogExp, "fogExp"); + bindUniform(Uniforms::lightCount, "lightCount"); + bindUniform(Uniforms::light_position, "lights[0].position"); + bindUniform(Uniforms::light_color, "lights[0].color"); + bindUniform(Uniforms::light_type, "lights[0].type"); + bindUniform(Uniforms::light_falloff_style, "lights[0].falloff_style"); + bindUniform(Uniforms::light_range, "lights[0].range"); + bindUniform(Uniforms::light_intensity, "lights[0].intensity"); + bindUniform(Uniforms::light_cutoff, "lights[0].cutoff"); + bindUniform(Uniforms::light_diffuseLighting, "lights[0].diffuseLighting"); + bindUniform(Uniforms::light_direction, "lights[0].direction"); + bindUniform(Uniforms::light_width, "lights[0].width"); + bindUniform(Uniforms::light_falloff_0, "lights[0].falloff[0]"); + bindUniform(Uniforms::light_falloff_1, "lights[0].falloff[1]"); + bindUniform(Uniforms::light_falloff_2, "lights[0].falloff[2]"); + bindUniform(Uniforms::light_falloff_3, "lights[0].falloff[3]"); + bindUniform(Uniforms::light_falloff_4, "lights[0].falloff[4]"); + bindUniform(Uniforms::light_falloff_5, "lights[0].falloff[5]"); + bindUniform(Uniforms::light_falloff_6, "lights[0].falloff[6]"); + bindUniform(Uniforms::light_falloff_7, "lights[0].falloff[7]"); + bindUniform(Uniforms::light_falloff_8, "lights[0].falloff[8]"); + bindUniform(Uniforms::light_falloff_9, "lights[0].falloff[9]"); + bindUniform(Uniforms::light_falloff_count, "lights[0].falloff_count"); } }; \ No newline at end of file diff --git a/browedit/util/Util.cpp b/browedit/util/Util.cpp index 0116089b..1e24b48b 100644 --- a/browedit/util/Util.cpp +++ b/browedit/util/Util.cpp @@ -1434,6 +1434,21 @@ namespace util float diff = (x - before.x) / (after.x - before.x); return before.y + diff * (after.y - before.y); } + float interpolateSCurve(float x) { + // Alright, this isn't an S Curve formula anymore, but your typical light attenuation formula... but with the shape of an S Curve. + x = 2 * x - 1; + const float a = 1.5f; + const float b = 1.5f; + + if (x <= -1) + return 2; + if (x >= 1) + return 0; + if (x <= 0) + return -1.0f / (1.0f + a * -x + b * x * x) * (1.0f + x) + 2; + + return 1.0f / (1.0f + a * x + b * x * x) * (1.0f - x); + } bool EditableGraph(const char* label, std::vector* points, std::function&, float)> interpolationStyle, bool& activated) { diff --git a/browedit/util/Util.h b/browedit/util/Util.h index b726bed0..53a56782 100644 --- a/browedit/util/Util.h +++ b/browedit/util/Util.h @@ -92,6 +92,7 @@ namespace util float interpolateLagrange(const std::vector& f, float x); float interpolateSpline(const std::vector& f, float x); float interpolateLinear(const std::vector& f, float x); + float interpolateSCurve(float x); bool EditableGraph(const char* label, std::vector* points, std::function&, float)> interpolationStyle, bool& activated); void Graph(const char* label, std::function func); diff --git a/browedit/windows/LightmapSettingsWindow.cpp b/browedit/windows/LightmapSettingsWindow.cpp index 9c6867f7..44c9bc65 100644 --- a/browedit/windows/LightmapSettingsWindow.cpp +++ b/browedit/windows/LightmapSettingsWindow.cpp @@ -25,6 +25,7 @@ void BrowEdit::showLightmapSettingsWindow() ImGui::Checkbox("Shadows", &settings.shadows); ImGui::Checkbox("Debug Points", &lightmapper->buildDebugPoints); ImGui::Checkbox("Height Edit Mode Selection Only", &settings.heightSelectionOnly); + ImGui::Checkbox("Old shadow formula (v3.561 and below)", &settings.additiveShadow); ImGui::DragInt2("Generate Range X", glm::value_ptr(settings.rangeX), 1, 0, gnd->width); ImGui::DragInt2("Generate Range Y", glm::value_ptr(settings.rangeY), 1, 0, gnd->height); ImGui::DragInt("Thread Count", &config.lightmapperThreadCount, 1, 1, 32); diff --git a/browedit/windows/MenuBar.cpp b/browedit/windows/MenuBar.cpp index fcc4b591..759e7bfb 100644 --- a/browedit/windows/MenuBar.cpp +++ b/browedit/windows/MenuBar.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,61 @@ void BrowEdit::menuBar() ImGui::EndMenu(); } + if (editMode == EditMode::Object && activeMapView && ImGui::BeginMenu("Light Edit")) + { + if (ImGui::MenuItem("Add new light")) { + auto l = new RswLight(); + l->range = 60.0f; + l->color = glm::vec3(0.8f, 0.8f, 0.8f); + Node* newNode = new Node("light"); + newNode->addComponent(new RswObject()); + newNode->addComponent(l); + newNode->addComponent(new BillboardRenderer("data\\light.png", "data\\light_selected.png")); + newNode->addComponent(new CubeCollider(5)); + newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); + newNodesCenter = glm::vec3(0, -35, 0); + newNodePlacement = BrowEdit::Relative; + } + + if (ImGui::MenuItem("Add new sun")) { + auto l = new RswLight(); + l->range = 0.0f; + l->intensity = 0.5f; + l->givesShadow = true; + l->affectShadowMap = true; + l->sunMatchRswDirection = true; + l->diffuseLighting = false; + l->lightType = RswLight::Type::Sun; + Node* newNode = new Node("sun"); + newNode->addComponent(new RswObject()); + newNode->addComponent(l); + newNode->addComponent(new BillboardRenderer("data\\light.png", "data\\light_selected.png")); + newNode->addComponent(new CubeCollider(5)); + newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); + newNodesCenter = glm::vec3(0, 0, 0); + newNodePlacement = BrowEdit::Ground; + } + + if (ImGui::MenuItem("Select all lights")) { + auto ga = new GroupAction(); + + activeMapView->map->rootNode->traverse([&](Node* n) + { + auto lightObject = n->getComponent(); + if (!lightObject) + return; + + auto action = new SelectAction(activeMapView->map, n, true, false); + action->perform(activeMapView->map, this); + ga->addAction(action); + }); + + if (!ga->isEmpty()) + activeMapView->map->doAction(ga, this); + } + + ImGui::EndMenu(); + } if(activeMapView) if (ImGui::BeginMenu(activeMapView->map->name.c_str())) diff --git a/browedit/windows/ObjectSelectWindow.cpp b/browedit/windows/ObjectSelectWindow.cpp index 5c1752d9..3e17d4af 100644 --- a/browedit/windows/ObjectSelectWindow.cpp +++ b/browedit/windows/ObjectSelectWindow.cpp @@ -325,7 +325,7 @@ void BrowEdit::showObjectWindow() newNode->addComponent(new RswModelCollider()); newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); newNodesCenter = glm::vec3(0, 0, 0); - newNodeHeight = false; + newNodePlacement = BrowEdit::Ground; } else if (file.substr(file.size() - 5) == ".json" && path.find("data\\lights") != std::string::npos) @@ -342,7 +342,7 @@ void BrowEdit::showObjectWindow() newNode->addComponent(new CubeCollider(5)); newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); newNodesCenter = glm::vec3(0, 0, 0); - newNodeHeight = false; + newNodePlacement = BrowEdit::Ground; } else if (file.substr(file.size() - 5) == ".json" && path.find("data\\effects") != std::string::npos) @@ -382,7 +382,7 @@ void BrowEdit::showObjectWindow() newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); newNodesCenter = glm::vec3(0, 0, 0); - newNodeHeight = false; + newNodePlacement = BrowEdit::Ground; } else if (file.substr(file.size() - 5) == ".json" && path.find("data\\prefabs") != std::string::npos) @@ -455,7 +455,7 @@ void BrowEdit::showObjectWindow() newNodesCenter = center; for (auto& n : newNodes) n.second = n.second - center; - newNodeHeight = false; + newNodePlacement = BrowEdit::Ground; } } else if (file.substr(file.size() - 4) == ".wav") @@ -468,7 +468,7 @@ void BrowEdit::showObjectWindow() newNode->addComponent(new CubeCollider(5)); newNodes.push_back(std::pair(newNode, glm::vec3(0, 0, 0))); newNodesCenter = glm::vec3(0, 0, 0); - newNodeHeight = false; + newNodePlacement = BrowEdit::Ground; } } std::cout << "Click on " << file << std::endl; diff --git a/data/shaders/gnd.fs b/data/shaders/gnd.fs index edbfccbe..50abc994 100644 --- a/data/shaders/gnd.fs +++ b/data/shaders/gnd.fs @@ -12,6 +12,7 @@ uniform float colorToggle = 1.0f; uniform float lightColorToggle = 1.0f; uniform float shadowMapToggle = 1.0f; uniform float viewTextures = 1.0f; +uniform int lightCount = 0; uniform bool fogEnabled; uniform float fogNear = 0; @@ -25,7 +26,29 @@ in vec3 normal; in vec4 color; out vec4 fragColor; -//out vec4 fragSelection; +in vec3 fragPos; + +#define MAX_FALLOFF 10 +#define NR_LIGHTS 1 + +struct Light { + vec3 position; + vec3 color; + int type; + int falloff_style; + vec2 falloff[MAX_FALLOFF]; + int falloff_count; + float range; + float intensity; + float cutoff; + bool diffuseLighting; + vec3 direction; + float width; +}; + +uniform Light lights[NR_LIGHTS]; + +vec3 CalcPointLight(Light light, vec3 normal, vec3 inFragPos); void main() { @@ -49,14 +72,173 @@ void main() texture.rgb *= max(texture2D(s_lighting, texCoord2).a, shadowMapToggle); texture.rgb += clamp(texture2D(s_lighting, texCoord2).rgb, 0.0, 1.0) * lightColorToggle; + for (int i = 0; i < lightCount; i++) + texture.rgb += CalcPointLight(lights[i], normalize(normal), fragPos); + if(fogEnabled) { float depth = gl_FragCoord.z / gl_FragCoord.w; float fogAmount = smoothstep(fogNear, fogFar, depth); texture = mix(texture, fogColor, fogAmount); } - - //gl_FragData[0] = texture; - //gl_FragData[1] = vec4(0,0,0,0); + fragColor = texture; +} + +// calculates the color when using a point light. +vec3 CalcPointLight(Light light, vec3 normal, vec3 inFragPos) +{ + normal = vec3(normal.x, -normal.y, normal.z); + vec3 lightDir = normalize(light.position - inFragPos); + float distance = length(light.position - inFragPos); + + if (light.type == 2) // sun, ignored + return vec3(0, 0, 0); + + float attenuation = 0.0f; + float dotProduct = abs(dot(normalize(normal), lightDir)); + + if (light.falloff_style == 4) { + float kC = 1; + float kL = 2.0f / light.range; + float kQ = 1.0f / (light.range * light.range); + float maxChannel = max(max(light.color.r, light.color.g), light.color.b); + float realRange = (-kL + sqrt(kL * kL - 4 * kQ * (kC - 128.0f * maxChannel * light.intensity))) / (2 * kQ); + + if (distance > realRange) + return vec3(0, 0, 0); + + float d = max(distance - light.range, 0.0f); + float denom = d / light.range + 1; + attenuation = light.intensity / (denom * denom); + if (light.cutoff > 0) + attenuation = max(0.0f, (attenuation - light.cutoff) / (1 - light.cutoff)); + } + else { + if (distance > light.range) + return vec3(0, 0, 0); + + float d = distance / light.range; + + if (light.falloff_style == 0) { + attenuation = clamp(1.0f - pow(d, light.cutoff), 0.0f, 1.0f); + } + else if (light.falloff_style == 1) { // Spline + int n = light.falloff_count; + float h[MAX_FALLOFF]; + float F[MAX_FALLOFF]; + float s[MAX_FALLOFF]; + float m[MAX_FALLOFF * MAX_FALLOFF]; + + for (int i = n - 1; i > 0; i--) { + h[i] = 0.0f; + F[i] = 0.0f; + s[i] = 0.0f; + } + + for (int i = n - 1; i > 0; i--) + { + F[i] = (light.falloff[i].y - light.falloff[i - 1].y) / (light.falloff[i].x - light.falloff[i - 1].x); + h[i - 1] = light.falloff[i].x - light.falloff[i - 1].x; + } + + for (int i = 1; i < n - 1; i++) + { + m[i * MAX_FALLOFF + i] = 2 * (h[i - 1] + h[i]); + if (i != 1) + { + m[i * MAX_FALLOFF + i - 1] = h[i - 1]; + m[(i - 1) * MAX_FALLOFF + i] = h[i - 1]; + } + m[i * MAX_FALLOFF + n - 1] = 6 * (F[i + 1] - F[i]); + } + + for (int i = 1; i < n - 2; i++) + { + float temp = (m[(i + 1) * MAX_FALLOFF + i] / m[i * MAX_FALLOFF + i]); + for (int j = 1; j <= n - 1; j++) + m[(i + 1) * MAX_FALLOFF + j] -= temp * m[i * MAX_FALLOFF + j]; + } + float sum; + for (int i = n - 2; i > 0; i--) + { + sum = 0; + for (int j = i; j <= n - 2; j++) + sum += m[i * MAX_FALLOFF + j] * s[j]; + s[i] = (m[i * MAX_FALLOFF + n - 1] - sum) / m[i * MAX_FALLOFF + i]; + } + for (int i = 0; i < n - 1; i++) { + if (light.falloff[i].x <= d && d <= light.falloff[i + 1].x) + { + float a = (s[i + 1] - s[i]) / (6 * h[i]); + float b = s[i] / 2; + float c = (light.falloff[i + 1].y - light.falloff[i].y) / h[i] - (2 * h[i] * s[i] + s[i + 1] * h[i]) / 6; + float e = light.falloff[i].y; + sum = a * pow((d - light.falloff[i].x), 3.0f) + b * pow((d - light.falloff[i].x), 2.0f) + c * (d - light.falloff[i].x) + e; + } + } + attenuation = clamp(sum, 0.0f, 1.0f); + } + else if (light.falloff_style == 2) { // Lagrange + for (int i = 0; i < light.falloff_count; i++) { + float term = light.falloff[i].y; + + for (int j = 1; j < light.falloff_count; j++) { + if (j != i) + term = term * (d - light.falloff[j].x) / (light.falloff[i].x - light.falloff[j].x); + } + + attenuation += term; + } + + attenuation = clamp(attenuation, 0.0f, 1.0f); + } + else if (light.falloff_style == 3) { // Linear + vec2 before = vec2(-9999, -9999); + vec2 after = vec2(9999, 9999); + + for (int i = 0; i < light.falloff_count; i++) { + vec2 p = light.falloff[i]; + + if (p.x <= d && d - p.x < d - before.x) + before = p; + if (p.x >= d && p.x - d < after.x - d) + after = p; + } + + float diff = (d - before.x) / (after.x - before.x); + attenuation = before.y + diff * (after.y - before.y); + attenuation = clamp(attenuation, 0.0f, 1.0f); + } + else if (light.falloff_style == 5) { // S Curve + d = 2.0f * d - 1.0f; + + if (d <= 0.0f) + attenuation = -1.0f / (1.0f + 1.5f * - d + 1.5f * d * d) * (1.0f + d) + 2.0f; + else + attenuation = 1.0f / (1.0f + 1.5f * d + 1.5f * d * d) * (1.0f - d); + } + else { + attenuation = 0.0f; + } + } + + if (light.type == 1) { // spot + float dp = dot(lightDir, -light.direction); + + if (dp < (1 - light.width)) + attenuation = 0.0f; + else { + float fac = 1.0f - ((1.0f - abs(dp)) / light.width); + attenuation *= fac; + } + } + + if (light.diffuseLighting) { + attenuation *= dotProduct; + } + + attenuation *= light.intensity; + + return light.color * attenuation; } \ No newline at end of file diff --git a/data/shaders/gnd.vs b/data/shaders/gnd.vs index 9860a9e5..361415a1 100644 --- a/data/shaders/gnd.vs +++ b/data/shaders/gnd.vs @@ -13,6 +13,7 @@ out vec2 texCoord; out vec2 texCoord2; out vec3 normal; out vec4 color; +out vec3 fragPos; void main() { @@ -20,5 +21,6 @@ void main() texCoord2 = a_texture2; normal = a_normal; color = a_color; + fragPos = a_position; gl_Position = projectionMatrix * modelViewMatrix * vec4(a_position,1); } \ No newline at end of file From 35a8faf91b0bdda0a5fd88a3c516dbc91673d07e Mon Sep 17 00:00:00 2001 From: Tokeiburu Date: Sat, 7 Dec 2024 14:35:49 -0500 Subject: [PATCH 2/3] Added option to hide other lightmaps when previewing a light. --- browedit/MapView.h | 1 + browedit/components/GndRenderer.cpp | 1 + browedit/components/GndRenderer.h | 1 + browedit/components/Rsw.Light.cpp | 8 ++++++-- browedit/shaders/GndShader.h | 2 ++ data/shaders/gnd.fs | 23 ++++++++++++++++++----- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/browedit/MapView.h b/browedit/MapView.h index 4a9980d5..1f440bf2 100644 --- a/browedit/MapView.h +++ b/browedit/MapView.h @@ -222,6 +222,7 @@ class MapView bool viewEffectIcons = true; bool enableLightQuickPreview = false; + bool hideOtherLightmaps = false; void focusSelection(); void drawLight(Node* n); diff --git a/browedit/components/GndRenderer.cpp b/browedit/components/GndRenderer.cpp index 350a3a1f..d6714df4 100644 --- a/browedit/components/GndRenderer.cpp +++ b/browedit/components/GndRenderer.cpp @@ -144,6 +144,7 @@ void GndRenderer::render() auto rswLight = quickRenderLightNode->getComponent(); auto rswObject = quickRenderLightNode->getComponent(); + shader->setUniform(GndShader::Uniforms::hideOtherLights, quickRenderLight_hideOthers); shader->setUniform(GndShader::Uniforms::light_position, glm::vec3(gnd->width * 5.0f + rswObject->position.x, -rswObject->position.y, gnd->height * 5.0f + 10.0f - rswObject->position.z)); shader->setUniform(GndShader::Uniforms::light_color, rswLight->color); shader->setUniform(GndShader::Uniforms::lightCount, 1); diff --git a/browedit/components/GndRenderer.h b/browedit/components/GndRenderer.h index 9d9a9a39..f78a8312 100644 --- a/browedit/components/GndRenderer.h +++ b/browedit/components/GndRenderer.h @@ -89,6 +89,7 @@ class GndRenderer : public Renderer bool smoothColors = false; bool viewTextures = true; bool viewFog = true; + bool quickRenderLight_hideOthers = false; Node* quickRenderLightNode = nullptr; bool viewEmptyTiles = true; diff --git a/browedit/components/Rsw.Light.cpp b/browedit/components/Rsw.Light.cpp index e20e5e19..cf2f42df 100644 --- a/browedit/components/Rsw.Light.cpp +++ b/browedit/components/Rsw.Light.cpp @@ -183,7 +183,6 @@ void RswLight::buildImGuiMulti(BrowEdit* browEdit, const std::vector& nod } util::CheckboxMulti(browEdit, browEdit->activeMapView->map, rswLights, "Enabled", [](RswLight* l) { return &l->enabled; }); - //if (ImGui::Button("Save as template")) { if (ImGui::Checkbox("Quick preview", &browEdit->activeMapView->enableLightQuickPreview)) { if (!browEdit->activeMapView->enableLightQuickPreview) { auto gndRenderer = browEdit->activeMapView->map->rootNode->getComponent(); @@ -197,11 +196,16 @@ void RswLight::buildImGuiMulti(BrowEdit* browEdit, const std::vector& nod auto gndRenderer = browEdit->activeMapView->map->rootNode->getComponent(); if (gndRenderer) { - if (browEdit->activeMapView->enableLightQuickPreview) + if (browEdit->activeMapView->enableLightQuickPreview) { gndRenderer->quickRenderLightNode = rswLights[0]->node; + gndRenderer->quickRenderLight_hideOthers = browEdit->activeMapView->hideOtherLightmaps; + } } } + ImGui::SameLine(); + ImGui::Checkbox("Hide other lightmaps", &browEdit->activeMapView->hideOtherLightmaps); + util::ColorEdit3Multi(browEdit, browEdit->activeMapView->map, rswLights, "Color", [](RswLight* l) { return &l->color; }); if(rswLights.front()->lightType != RswLight::Type::Sun) util::DragFloatMulti(browEdit, browEdit->activeMapView->map, rswLights, "Range", [](RswLight* l) { return &l->range; }, 1.0f, 0.0f, 1000.0f); diff --git a/browedit/shaders/GndShader.h b/browedit/shaders/GndShader.h index 6b8befa9..ab26acca 100644 --- a/browedit/shaders/GndShader.h +++ b/browedit/shaders/GndShader.h @@ -49,6 +49,7 @@ class GndShader : public gl::Shader light_falloff_8, light_falloff_9, light_falloff_count, + hideOtherLights, End }; }; @@ -94,5 +95,6 @@ class GndShader : public gl::Shader bindUniform(Uniforms::light_falloff_8, "lights[0].falloff[8]"); bindUniform(Uniforms::light_falloff_9, "lights[0].falloff[9]"); bindUniform(Uniforms::light_falloff_count, "lights[0].falloff_count"); + bindUniform(Uniforms::hideOtherLights, "hideOtherLights"); } }; \ No newline at end of file diff --git a/data/shaders/gnd.fs b/data/shaders/gnd.fs index 50abc994..22e3b6b4 100644 --- a/data/shaders/gnd.fs +++ b/data/shaders/gnd.fs @@ -13,6 +13,7 @@ uniform float lightColorToggle = 1.0f; uniform float shadowMapToggle = 1.0f; uniform float viewTextures = 1.0f; uniform int lightCount = 0; +uniform bool hideOtherLights = false; uniform bool fogEnabled; uniform float fogNear = 0; @@ -59,6 +60,11 @@ void main() if(texture.a < 0.1) discard; + vec3 light = vec3(0, 0, 0); + + for (int i = 0; i < lightCount; i++) + light.rgb += CalcPointLight(lights[i], normalize(normal), fragPos); + float NL = clamp(dot(normalize(normal), vec3(1,-1,1)*lightDirection),0.0,1.0); vec3 ambientFactor = (1.0 - lightAmbient) * lightAmbient; vec3 ambient = lightAmbient - ambientFactor + ambientFactor * lightDiffuse; @@ -70,11 +76,18 @@ void main() texture.rgb *= min(mult1, mult2); texture.rgb *= max(color, colorToggle).rgb; texture.rgb *= max(texture2D(s_lighting, texCoord2).a, shadowMapToggle); - texture.rgb += clamp(texture2D(s_lighting, texCoord2).rgb, 0.0, 1.0) * lightColorToggle; - - for (int i = 0; i < lightCount; i++) - texture.rgb += CalcPointLight(lights[i], normalize(normal), fragPos); - + + if (!hideOtherLights) { + texture.rgb += clamp(texture2D(s_lighting, texCoord2).rgb, 0.0, 1.0) * lightColorToggle; + texture.rgb += light.rgb; + } + else { + if (lightCount > 0 && light.rgb != vec3(0, 0, 0)) + texture.rgb += light.rgb; + else + texture.rgb += clamp(texture2D(s_lighting, texCoord2).rgb, 0.0, 1.0) * lightColorToggle; + } + if(fogEnabled) { float depth = gl_FragCoord.z / gl_FragCoord.w; From 781abac8970cb32df33629de2971ea6fc0e33081 Mon Sep 17 00:00:00 2001 From: Tokeiburu Date: Sat, 7 Dec 2024 16:21:30 -0500 Subject: [PATCH 3/3] Some cleanup. --- browedit/components/Gnd.cpp | 11 ----------- browedit/components/Rsw.h | 1 - 2 files changed, 12 deletions(-) diff --git a/browedit/components/Gnd.cpp b/browedit/components/Gnd.cpp index f13ecb07..894595ac 100644 --- a/browedit/components/Gnd.cpp +++ b/browedit/components/Gnd.cpp @@ -1555,19 +1555,8 @@ const unsigned int Gnd::Lightmap::hash() const for (int i = 0; i < size; i++) { crc ^= data[i]; crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; - //crc += (data[i] ^ POLY) & 0xff; } return ~crc; - - //const int precision = 8; - //unsigned long crc = 0; - //int size = gnd->lightmapWidth * gnd->lightmapHeight * 4; - //if (size < 4) - // return 0; - //for (int i = 0; i < precision; i++) { - // crc |= (data[i * size / precision] & 0xf) << (i * precision); - //} - //return crc; } diff --git a/browedit/components/Rsw.h b/browedit/components/Rsw.h index 26063140..af485bcb 100644 --- a/browedit/components/Rsw.h +++ b/browedit/components/Rsw.h @@ -221,7 +221,6 @@ class RswLight : public Component bool affectShadowMap = false; bool affectLightmap = true; bool enabled = true; - bool quickPreview = true; bool shadowTerrain = true; bool sunMatchRswDirection = true;