From a3672012f60d96c049c13458a05ca15029be5d92 Mon Sep 17 00:00:00 2001 From: Joseph Aquino Date: Fri, 11 Apr 2025 13:58:45 -0400 Subject: [PATCH] Major code rewrite. -class Light added to handle the construction of the triangle fan -light can now be bounded between two bounderies -differnt sections of main have been placed into different functions for readability -imgui variables were made inline and moved to Utility.hpp for convenience -the claculation of intersections for rays hav been moved to Ray::calculateIntersect -variables containing std::vector, class Light, and std::vector were moved outsid of main() for convenience --- Light.cpp | 94 +++++++ Light.h | 77 ++++++ Ray.cpp | 20 ++ Ray.h | 4 +- Utility.hpp | 19 ++ imgui.ini | 4 +- main.cpp | 434 ++++++++++++++++-------------- ray-casting-demos.vcxproj | 2 + ray-casting-demos.vcxproj.filters | 6 + 9 files changed, 455 insertions(+), 205 deletions(-) create mode 100644 Light.cpp create mode 100644 Light.h diff --git a/Light.cpp b/Light.cpp new file mode 100644 index 0000000..08f667f --- /dev/null +++ b/Light.cpp @@ -0,0 +1,94 @@ +#include "Light.h" + +void Light::draw(sf::RenderWindow& window) +{ + if (not m_visible) return; + + changeColor(lightColor.asSfColor()); + window.draw(m_light.data(), m_light.size(), sf::PrimitiveType::TriangleFan); + if (not drawEndPoints) return; + for (auto& point : m_light) + { + point.color = sf::Color::Black; + sf::CircleShape endPoint(5.f, 20u); + endPoint.setOrigin({ 5, 5 }); + endPoint.setFillColor(rayColor.asSfColor()); + endPoint.setPosition(point.position); + window.draw(endPoint); + } +} + +void Light::addRay(Ray ray) +{ + m_rays.emplace_back(ray); +} + +void Light::changeColor(sf::Color color) +{ + for (auto& points : m_light) + points.color = color; + + for (auto& ray : m_rays) + ray.changeColor(color); +} + +void Light::processRays(const std::vector& polygons) +{ + if (m_center and m_offset) + { + constructPartialLight(polygons); + } + else + { + constructFullLight(); + } + +} + +void Light::constructFullLight() +{ + std::sort(m_rays.begin(), m_rays.end(), [](const auto& lhs, const auto& rhs) { return lhs.angle().wrapUnsigned().asRadians() < rhs.angle().wrapUnsigned().asRadians(); }); + + for (auto& ray : m_rays) + { + m_light.emplace_back(ray.points()[end]); + } + + m_light.emplace_back(m_rays.front().points()[end]);//fully connect the triangle fan + +} + +void Light::constructPartialLight(const std::vector& polygons) +{ + auto upperAngleBound = m_center.value() + m_offset.value(); + auto lowerAngleBound = m_center.value() - m_offset.value(); + Ray upperBound(m_light[0].position, rayLength, upperAngleBound, lightColor.asSfColor()); + Ray lowerBound(m_light[0].position, rayLength, lowerAngleBound, lightColor.asSfColor()); + + m_rays.emplace_back(upperBound); + m_rays.emplace_back(lowerBound); + for (auto& ray : m_rays) + { + ray.calculateIntersect(polygons); + } + + if ((m_center.value().wrapUnsigned().asDegrees() - m_offset.value().wrapUnsigned().asDegrees() < 0.f) or (m_center.value().wrapUnsigned().asDegrees() + m_offset.value().wrapUnsigned().asDegrees() > 360.f)) + { + std::sort(m_rays.begin(), m_rays.end(), [](const auto& lhs, const auto& rhs) { return lhs.angle().wrapSigned().asRadians() < rhs.angle().wrapSigned().asRadians(); }); + for (auto& ray : m_rays) + if (ray.angle().wrapSigned() >= lowerAngleBound.wrapSigned() and ray.angle().wrapSigned() <= upperAngleBound.wrapSigned()) + { + m_light.emplace_back(ray.points()[end]); + } + } + else + { + std::sort(m_rays.begin(), m_rays.end(), [](const auto& lhs, const auto& rhs) { return lhs.angle().wrapUnsigned().asRadians() < rhs.angle().wrapUnsigned().asRadians(); }); + for (auto& ray : m_rays) + if (ray.angle().wrapUnsigned() >= lowerAngleBound.wrapUnsigned() and ray.angle().wrapUnsigned() <= upperAngleBound.wrapUnsigned()) + { + m_light.emplace_back(ray.points()[end]); + } + } + +} \ No newline at end of file diff --git a/Light.h b/Light.h new file mode 100644 index 0000000..7223f9e --- /dev/null +++ b/Light.h @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Utility.hpp" +#include "Ray.h" + +class Light +{ +public: + + Light(bool visible = true) + : m_visible(visible) + { + m_light.push_back(sf::Vertex()); + } + + Light(sf::Angle center, sf::Angle offset, bool visible = true) + : m_center(center) + , m_offset(offset) + , m_visible(visible) + { + m_light.push_back(sf::Vertex{}); + } + + void addAngleBounds(sf::Angle center, sf::Angle offset) + { + m_center = center; + m_offset = offset; + } + + void removeAngleBounds() + { + m_center.reset(); + m_offset.reset(); + } + + void setVisibility(bool input) { m_visible = input; } + bool visible() const { return m_visible; } + + void setCenterAngle(sf::Angle center) { m_center = center; } + std::optional getCenterAngle() const { return m_center; } + + void setOffsetAngle(sf::Angle offset) { m_offset = offset; } + std::optional getOffsetAngle() const { return m_offset; } + + sf::Vector2f getOrigin() const { return m_light[0].position; } + void setOrigin(sf::Vector2f center) { m_light[0].position = center; } + + void draw(sf::RenderWindow& window); + + void addRay(Ray ray); + + void clearRays() { m_rays.clear(); m_light.clear(); m_light.push_back(sf::Vertex{});} + + void changeColor(sf::Color color); + + void processRays(const std::vector& polygons); +private: + std::vector m_light{}; + RayVector m_rays{}; + std::optional m_center{}; + std::optional m_offset{}; + + bool m_visible{}; + + void constructFullLight(); + void constructPartialLight(const std::vector& polygons); +}; + diff --git a/Ray.cpp b/Ray.cpp index a59395f..d74f0eb 100644 --- a/Ray.cpp +++ b/Ray.cpp @@ -167,3 +167,23 @@ m_points[start].color = color; m_points[end].color = color; } + + void Ray::calculateIntersect(const std::vector& polygons) + { + sf::Vector2f closestIntersection = m_points[end].position; + std::optional currentIntersect{}; + for (auto& poly : polygons) + { + for (int i = 1; i <= poly.getVertexCount() - 1; i++) { + currentIntersect = lineIntersect({ m_points[start].position, m_points[end].position }, { poly[i - 1].position , poly[i].position }); + if (currentIntersect) + { + if ((closestIntersection - m_points[start].position).length() > (currentIntersect.value() - m_points[start].position).length()) + { + closestIntersection = currentIntersect.value(); + } + } + } + } + changeEnd(closestIntersection); + } diff --git a/Ray.h b/Ray.h index 1bea465..fb402bb 100644 --- a/Ray.h +++ b/Ray.h @@ -33,7 +33,7 @@ public: void change(const Ray& ray, bool copyColor = false); void change(sf::Vector2f startPoint, sf::Vector2f endPoint); void change(sf::Vector2f startPoint, sf::Vector2f endPoint, sf::Color color); - + void changeStart(sf::Vector2f startPoint, sf::Color color); void changeStart(sf::Vector2f startPoint); void changeStart(const Ray& ray, bool copyColor = false); @@ -44,6 +44,8 @@ public: void changeColor(sf::Color color); + void calculateIntersect(const std::vector& polygons); + bool operator== (const Ray& rhs) { return m_points[start].position == rhs.points()[start].position and m_points[end].position == rhs.points()[end].position; diff --git a/Utility.hpp b/Utility.hpp index f40883d..6d6f4a3 100644 --- a/Utility.hpp +++ b/Utility.hpp @@ -9,6 +9,8 @@ #include "Random.h" #include "Ray.h" + + template inline T getRandomNumber(U min, V max) { @@ -131,3 +133,20 @@ inline float distanceBetween(sf::Vector2f startPoint, sf::Vector2f endPoint) { return sqrtf(powf(endPoint.x - startPoint.x, 2) + powf(endPoint.y - startPoint.y, 2)); } + + +//imGui Variables +inline sf::Vector2f mousePos{}; +inline float polySize[2] = { 100.f, 100.f }; +inline int numRays{ 500 }; +inline int vertexBounds[2] = { 3u, 10u }; +inline int averageSize{ 100u }; +inline float rayLength{ 500.f }; +inline imguiColor rayColor = constructImguiColor(sf::Color::Red); +inline imguiColor lightColor = constructImguiColor(sf::Color::Yellow); +inline bool drawRays{ false }; +inline bool drawLight{ true }; +inline bool drawEndPoints{ false }; +inline bool boundedLight{ false }; +inline int lightCenterAngle{0}; +inline int lightOffsetAngle{45}; \ No newline at end of file diff --git a/imgui.ini b/imgui.ini index 14b600c..1467fe0 100644 --- a/imgui.ini +++ b/imgui.ini @@ -3,6 +3,6 @@ Pos=60,60 Size=400,400 [Window][Config] -Pos=7,7 -Size=710,322 +Pos=44,11 +Size=708,467 diff --git a/main.cpp b/main.cpp index fac0d63..f7964ba 100644 --- a/main.cpp +++ b/main.cpp @@ -10,231 +10,261 @@ #include "Utility.hpp" #include "Ray.h" +#include "Light.h" + + +auto window = sf::RenderWindow(sf::VideoMode({ 1920u, 1080u }), "Ray Casting Test"); + + Light light; + RayVector rays; + std::vector polygons; + +void GUI(); +void userInput(std::vector& polygons); +void processRays(); +void render(); int main() -{ +{ sf::Vector2f center{ 1920.f / 2.f, 1080.f / 2.f }; //sfml setup - auto window = sf::RenderWindow(sf::VideoMode({ 1920u, 1080u }), "Ray Casting Test"); - + sf::Clock clock; window.setFramerateLimit(144); if (!ImGui::SFML::Init(window)) return -1; - sf::Clock clock; - sf::Vector2f mousePos = center; + mousePos = center; - //imGui Variables - float polySize[2] = { 100.f, 100.f }; - int numRays{ 500 }; - int vertexBounds[2] = { 3u, 10u }; - int averageSize{ 100u }; - float rayLength{ 500.f }; - imguiColor rayColor = constructImguiColor(sf::Color::Red); - imguiColor lightColor = constructImguiColor(sf::Color::Red); - bool drawRays{ false }; - bool drawLight{ true }; - - - //initalize vectors - RayVector rays; - - std::vector polygons; - polygons.push_back(constructRectangle(center, center)); - + polygons.emplace_back(constructRectangle(center, center)); //main game loop while (window.isOpen()) { - rays.clear(); - //handle input - while (const std::optional event = window.pollEvent()) - { - ImGui::SFML::ProcessEvent(window, *event); - - if (event->is()) - { - window.close(); - } - - if (const auto* buttonPressed = event->getIf()) - { - switch (buttonPressed->scancode) - { - case sf::Keyboard::Scancode::C: - polygons.clear(); - break; - - case sf::Keyboard::Scancode::R: - polygons.push_back(constructRandomPolygon(mousePos, vertexBounds[0] + 1, vertexBounds[1] + 1, averageSize)); - break; - - case sf::Keyboard::Scancode::Escape: - window.close(); - break; - - default: - break; - } - } - - if (const auto* mousePressed = event->getIf()) - { - switch (mousePressed->button) - { - case sf::Mouse::Button::Right: - polygons.push_back(constructRectangle((sf::Vector2f)mousePressed->position, { polySize[0], polySize[1] }, sf::Color::Black)); - break; - - default: - break; - } - } - - if (const auto* mouseMoved = event->getIf()) - { - mousePos = (sf::Vector2f)mouseMoved->position; - for (auto& ray : rays) ray.changeStart(mousePos); - } - }// end user input loop - - if (numRays > 0) - { - float i = 0.f, angleIncrement = 360.f / (float)numRays; - for (int i = 0; i < numRays; i++) rays.push_back(Ray(mousePos, rayLength, sf::degrees(angleIncrement * i), rayColor.asSfColor())); - } - - for (auto& poly : polygons) - { - for (std::size_t i = 0; i < poly.getVertexCount() - 1; i++) - { - if (distanceBetween(mousePos, poly[i].position) <= rayLength) - { - sf::Angle angle = sf::radians(std::atan2f(poly[i].position.y - mousePos.y, poly[i].position.x - mousePos.x)); - rays.push_back(Ray(mousePos, poly[i].position, rayColor.asSfColor())); - rays.push_back(Ray(mousePos, rayLength, angle + sf::radians(.00001f), rayColor.asSfColor())); - rays.push_back(Ray(mousePos, rayLength, angle - sf::radians(.00001f), rayColor.asSfColor())); - } - } - } - - for (auto& ray : rays) - { - auto& rayPoints = ray.points(); - sf::Vector2f closestIntersection = rayPoints[end].position; - std::optional currentIntersect{}; - for (auto& poly : polygons) - { - for (int i = 1; i <= poly.getVertexCount() - 1; i++) { - currentIntersect = lineIntersect({ rayPoints[start].position, rayPoints[end].position }, { poly[i - 1].position , poly[i].position }); - if (currentIntersect) - { - if ((closestIntersection - rayPoints[start].position).length() > (currentIntersect.value() - rayPoints[start].position).length()) - { - closestIntersection = currentIntersect.value(); - } - } - } - } - ray.changeEnd(closestIntersection); - } - - std::sort(rays.begin(), rays.end(), [](const auto& lhs, const auto& rhs) { return lhs.angle().wrapUnsigned().asRadians() < rhs.angle().wrapUnsigned().asRadians(); }); - - sf::VertexArray light(sf::PrimitiveType::TriangleFan, 1); - if (not rays.empty()) - { - light[0].position = mousePos; - light[0].color = lightColor.asSfColor(); - for (auto& ray : rays) - { - light.append(ray.points()[end]); - } - light.append(rays.front().points()[end]); - } - - for (int i = 0; i < light.getVertexCount(); i++) - light[i].color = lightColor.asSfColor(); - - ImGui::SFML::Update(window, clock.restart()); - ImGui::Begin("Config"); - ImGui::Text("Controls:"); - ImGui::Indent(); - ImGui::Text("Right Click: place rectangle at mouse position"); - ImGui::Text("R: place a randomly sized polygon at mouse position"); - ImGui::Text("C: clear all polygons"); - ImGui::Unindent(); - ImGui::Checkbox("Render rays", &drawRays); - ImGui::SameLine(); - ImGui::Checkbox("Render light", &drawLight); - if (ImGui::SliderInt("number of rays", &numRays, 5, 500)) - { - rays.clear(); + rays.clear(); + light.clearRays(); - { - float i = 0.f, angleIncrement = 360.f / (float)numRays; - for (int i = 0; i < numRays; i++) rays.push_back(Ray(mousePos, rayLength, sf::degrees(angleIncrement * i), rayColor.asSfColor())); - } + userInput(polygons); - } - ImGui::InputFloat("Ray Length", &rayLength); - if (ImGui::ColorEdit3("Ray color", &rayColor.r)) - { - for (auto& ray : rays) ray.changeColor(rayColor.asSfColor()); - } - if (ImGui::ColorEdit3("Light color", &lightColor.r)) - { - for (int i = 0; i < light.getVertexCount(); i++) - light[i].color = lightColor.asSfColor(); - } - ImGui::InputFloat2("rectangle Size (X,Y)", polySize, "%.1f"); - ImGui::Text("Random polygon config: "); - if (ImGui::SliderInt2("vertex count lower/upper bounds", vertexBounds, 3, 20)) - { - if (vertexBounds[0] > vertexBounds[1]) - vertexBounds[0] = vertexBounds[1]; + processRays(); - if (vertexBounds[1] < vertexBounds[0]) - vertexBounds[0] = vertexBounds[1]; - } + light.setOrigin(mousePos); - if (ImGui::InputInt("Average size", &averageSize)) - { - if (averageSize < 20) - { - averageSize = 20; - } - } + for (auto& ray : rays) light.addRay(ray); - ImGui::End(); + GUI(); - window.clear(sf::Color::White); + render(); - //render entities - for (auto& poly : polygons) window.draw(poly); - - if (drawRays) - { - for (auto& ray : rays) - { - auto& rayPoints = ray.points(); - sf::CircleShape endPoint(5.f, 20u); - endPoint.setOrigin({ 5, 5 }); - endPoint.setFillColor(rayColor.asSfColor()); - endPoint.setPosition(rayPoints[end].position); - window.draw(rayPoints); - window.draw(endPoint); - } - } - - if (drawLight) window.draw(light); - - ImGui::SFML::Render(window); - - window.display(); } ImGui::SFML::Shutdown(); } + +void GUI() +{ + ImGui::Begin("Config"); + ImGui::Text("Controls:"); + ImGui::Indent(); + ImGui::Text("Right Click: place rectangle at mouse position"); + ImGui::Text("R: place a randomly sized polygon at mouse position"); + ImGui::Text("C: clear all polygons"); + ImGui::Text("Scroll wheel: increase/decrease light direction (if light is bounded)"); + ImGui::Unindent(); + ImGui::Checkbox("Render rays", &drawRays); + ImGui::SameLine(); + if (ImGui::Checkbox("Render light", &drawLight)) + { + light.setVisibility(drawLight); + } + if (ImGui::SliderInt("number of rays", &numRays, 5, 500)) + { + rays.clear(); + + { + float i = 0.f, angleIncrement = 360.f / (float)numRays; + for (int i = 0; i < numRays; i++) rays.emplace_back(mousePos, rayLength, sf::degrees(angleIncrement * i), rayColor.asSfColor()); + } + + } + ImGui::InputFloat("Ray Length", &rayLength); + if (ImGui::ColorEdit3("Ray color", &rayColor.r)) + { + for (auto& ray : rays) ray.changeColor(rayColor.asSfColor()); + } + ImGui::ColorEdit3("Light color", &lightColor.r); + if(ImGui::Checkbox("Light is bounded", &boundedLight)) + { + if (boundedLight) + { + light.addAngleBounds(sf::degrees((float)lightCenterAngle), sf::degrees((float)lightOffsetAngle)); + } + else + { + light.removeAngleBounds(); + } + } + if (boundedLight) + { + ImGui::Checkbox("Draw end points", &drawEndPoints); + ImGui::SliderInt("Light direction", &lightCenterAngle, 0, 359); + light.setCenterAngle(sf::degrees((float)lightCenterAngle)); + + if (ImGui::InputInt("Offset size", &lightOffsetAngle)) + { + if (lightOffsetAngle > 89) + { + lightOffsetAngle = 89; + } + if (lightOffsetAngle < 0) + { + lightOffsetAngle = 0; + } + } + light.setOffsetAngle(sf::degrees((float)lightOffsetAngle)); + } + ImGui::InputFloat2("rectangle Size (X,Y)", polySize, "%.1f"); + ImGui::Text("Random polygon config: "); + if (ImGui::SliderInt2("vertex count lower/upper bounds", vertexBounds, 3, 20)) + { + if (vertexBounds[0] > vertexBounds[1]) + vertexBounds[0] = vertexBounds[1]; + + if (vertexBounds[1] < vertexBounds[0]) + vertexBounds[0] = vertexBounds[1]; + } + + if (ImGui::InputInt("Average size", &averageSize)) + { + if (averageSize < 20) + { + averageSize = 20; + } + } + + ImGui::End(); +} + +void userInput(std::vector& polygons) +{ + while (const std::optional event = window.pollEvent()) + { + ImGui::SFML::ProcessEvent(window, *event); + + if (event->is()) + { + window.close(); + } + + if (const auto* buttonPressed = event->getIf()) + { + switch (buttonPressed->scancode) + { + case sf::Keyboard::Scancode::C: + polygons.clear(); + break; + + case sf::Keyboard::Scancode::R: + polygons.emplace_back(constructRandomPolygon(mousePos, vertexBounds[0] + 1, vertexBounds[1] + 1, averageSize)); + break; + + case sf::Keyboard::Scancode::Escape: + window.close(); + break; + + default: + break; + } + } + + if (const auto* mousePressed = event->getIf()) + { + switch (mousePressed->button) + { + case sf::Mouse::Button::Right: + polygons.emplace_back(constructRectangle((sf::Vector2f)mousePressed->position, { polySize[0], polySize[1] }, sf::Color::Black)); + break; + + default: + break; + } + } + + if (const auto* mouseWheelScrolled = event->getIf()) + { + switch (mouseWheelScrolled->wheel) + { + case sf::Mouse::Wheel::Vertical: + if (boundedLight) + lightCenterAngle = std::clamp(lightCenterAngle += (int)mouseWheelScrolled->delta, 0, 360); + break; + + default: + break; + } + } + + if (const auto* mouseMoved = event->getIf()) + { + mousePos = (sf::Vector2f)mouseMoved->position; + } + } +} + +void processRays() +{ + if (numRays > 0) + { + float i = 0.f, angleIncrement = 360.f / (float)numRays; + for (int i = 0; i < numRays; i++) rays.emplace_back(Ray(mousePos, rayLength, sf::degrees(angleIncrement * i), rayColor.asSfColor())); + } + + for (auto& poly : polygons) + { + for (std::size_t i = 0; i < poly.getVertexCount() - 1; i++) + { + if (distanceBetween(mousePos, poly[i].position) <= rayLength) + { + sf::Angle angle = sf::radians(std::atan2f(poly[i].position.y - mousePos.y, poly[i].position.x - mousePos.x)); + rays.emplace_back(Ray(mousePos, poly[i].position, rayColor.asSfColor())); + rays.emplace_back(Ray(mousePos, rayLength, angle + sf::radians(.00001f), rayColor.asSfColor())); + rays.emplace_back(Ray(mousePos, rayLength, angle - sf::radians(.00001f), rayColor.asSfColor())); + } + } + } + + for (auto& ray : rays) + { + ray.calculateIntersect(polygons); + } + +} + +void render() +{ + window.clear(sf::Color::White); + + light.processRays(polygons); + light.draw(window); + + for (auto& poly : polygons) window.draw(poly); + + if (drawRays) + { + for (auto& ray : rays) + { + auto& rayPoints = ray.points(); + sf::CircleShape endPoint(5.f, 20u); + endPoint.setOrigin({ 5, 5 }); + endPoint.setFillColor(rayColor.asSfColor()); + endPoint.setPosition(rayPoints[end].position); + window.draw(rayPoints); + window.draw(endPoint); + } + } + + ImGui::SFML::Render(window); + + window.display(); +} \ No newline at end of file diff --git a/ray-casting-demos.vcxproj b/ray-casting-demos.vcxproj index 81f4e2e..59d295b 100644 --- a/ray-casting-demos.vcxproj +++ b/ray-casting-demos.vcxproj @@ -142,10 +142,12 @@ + + diff --git a/ray-casting-demos.vcxproj.filters b/ray-casting-demos.vcxproj.filters index 715ff2f..58a0d8c 100644 --- a/ray-casting-demos.vcxproj.filters +++ b/ray-casting-demos.vcxproj.filters @@ -45,6 +45,9 @@ Source Files\Classes + + Source Files\Classes + @@ -56,5 +59,8 @@ Header Files\Classes + + Header Files\Classes + \ No newline at end of file