From 09072ae42ba9709d50dd0b96dd9616bc55932341 Mon Sep 17 00:00:00 2001
From: alfrh02 <alfred.n.hall@gmail.com>
Date: Sun, 22 Dec 2024 18:31:04 +0000
Subject: [PATCH] a0.10_9 Misc changes and big shader optimisation & cleanup +
 added gradient check before light calculation; + circles now indicate where
 lights are located when in lighting mode; + added controls section to README;
 + added tool icon in lower-left for better usability; * renamed light.size to
 light.radius for clarity; * organised lighting.frag into functions; * changed
 to use 'out vec4 fragColor' rather than deprecated gl_FragColor; - removed
 uApple & uSmoothShadows shader uniforms (no more manual toggling of soft/hard
 shadows);

---
 CMakeLists.txt                  |   2 +-
 README.md                       |  34 ++++++++---
 build.sh                        |   8 ++-
 res/shaders/broken.frag         |  10 ++--
 res/shaders/lighting.frag       |  97 ++++++++++++++++++--------------
 res/{ => textures}/brush.png    | Bin
 res/{ => textures}/canvas.png   | Bin
 res/{ => textures}/cursor.png   | Bin
 res/textures/icons/drawing.png  | Bin 0 -> 608 bytes
 res/textures/icons/lighting.png | Bin 0 -> 603 bytes
 res/textures/icons/viewing.png  | Bin 0 -> 598 bytes
 src/config.h.in                 |   2 +
 src/game.cpp                    |  86 +++++++++++++---------------
 src/game.h                      |   5 +-
 src/main.cpp                    |   2 +-
 15 files changed, 140 insertions(+), 106 deletions(-)
 rename res/{ => textures}/brush.png (100%)
 rename res/{ => textures}/canvas.png (100%)
 rename res/{ => textures}/cursor.png (100%)
 create mode 100644 res/textures/icons/drawing.png
 create mode 100644 res/textures/icons/lighting.png
 create mode 100644 res/textures/icons/viewing.png

diff --git a/CMakeLists.txt b/CMakeLists.txt
index edbc75d..ace25c7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED NO)
 set(CMAKE_CXX_EXTENSIONS        OFF)
 
 # major, minor, patch, and _STAGE_ (0 = alpha, 1 = beta, 2 = gamma/release)
-project(radiance_cascades VERSION 0.10.8.0)
+project(radiance_cascades VERSION 0.10.9.0)
 
 configure_file(src/config.h.in config.h)
 
diff --git a/README.md b/README.md
index 99329c6..1431b43 100644
--- a/README.md
+++ b/README.md
@@ -3,27 +3,27 @@
 1. [Requirements](#requirements)
 2. [Setup (macOS, Linux)](#setup-macos-linux)
 3. [Setup (Windows)](#setup-windows-visual-studio)
+3. [Controls](#controls)
 
-2D lighting demo.
+2D lighting demo. Intended for 1080p displays.
 
-Maze image texture initially generated via [mazegenerator.net](https://www.mazegenerator.net/). 
+Maze image texture initially generated via [mazegenerator.net](https://www.mazegenerator.net/).
 
 ## Requirements
 
 - CMake 3.25
     - A C++11 (or higher) compiler
 - Raylib 5.5 (provided via CMake if not installed system-wide)
+    - If Raylib is not [installed](https://github.com/raysan5/raylib#build-and-installation) on your machine, CMake will download and build Raylib for you in the build directory.
 
 This currently builds and runs on macOS, Arch Linux (on X11), and Windows. No planned support for Wayland :P
 
-## Setup (macOS, Linux) 
-
-If Raylib is not installed on your machine, CMake will download and build Raylib for you in the build directory.
+## Setup (macOS, Linux)
 
 ```bash
-# run the convenience build script 
+# run the convenience build script
 ./build.sh    # to build without running
-./build.sh -r # to build and run the resulting binary 
+./build.sh -r # to build and run the resulting binary
 
 # or build it manually via CMake
 mkdir build
@@ -36,8 +36,6 @@ cd .. # run the resulting binary in the source directory, not the build director
 
 ## Setup (Windows Visual Studio)
 
-If Raylib is not installed on your machine, CMake will download and build Raylib for you in the build directory.
-
 Via command prompt:
 
 ```bash
@@ -49,3 +47,21 @@ cmake ..
 This will generate a VS solution file (.sln) for you to use for compilation.
 
 Make sure to run any resulting .exe from the project root directory.
+
+## Controls
+
+1, 2, & 3 toggles between drawing, lighting, and viewing mode.
+
+Scrolling up or down increase brush/light size depending on if you are in drawing or lighting mode.
+
+C clears the canvas if in drawing mode, and removes all lights if in lighting mode.
+
+R resets the canvas to the original maze layout if in drawing mode, and resets to the starting lights if in lighting mode.
+
+F3 to open the debug UI. When the debug UI is open, scrolling the mouse will change the amount of shadow cascades.
+
+Left-mouse-button erases in drawing mode, or deletes nearby lights when in lighting mode.
+
+Right-mouse-button draws in drawing mode, or places a light when in lighting mode.
+
+Middle-mouse-button can be used to move lights around in any mode.
diff --git a/build.sh b/build.sh
index 220a62f..d1e9175 100755
--- a/build.sh
+++ b/build.sh
@@ -9,6 +9,10 @@ cmake ..
 make
 popd
 
-while getopts "r" arg; do
-  ./build/radiance_cascades
+while getopts ":r" arg; do
+  case ${arg} in
+    r)
+      ./build/radiance_cascades
+      ;;
+  esac
 done
diff --git a/res/shaders/broken.frag b/res/shaders/broken.frag
index ae255d6..a9c5009 100644
--- a/res/shaders/broken.frag
+++ b/res/shaders/broken.frag
@@ -4,21 +4,23 @@
 #define PRIMARY   vec4(1.0f, 0.0f, 1.0f, 1.0f)
 #define SECONDARY vec4(0.0f, 0.0f, 0.0f, 1.0f)
 
+out vec4 fragColor;
+
 // checkerboard pattern
 void main()
 {
   vec2 pos = mod(gl_FragCoord.xy,vec2(N));
 
   if      ((pos.x > N/2) && (pos.y > N/2)){
-    gl_FragColor = PRIMARY;
+    fragColor = PRIMARY;
   }
   else if ((pos.x < N/2) && (pos.y < N/2)){
-    gl_FragColor = PRIMARY;
+    fragColor = PRIMARY;
   }
   else if ((pos.x < N/2) && (pos.y > N/2)){
-    gl_FragColor = SECONDARY;
+    fragColor = SECONDARY;
   }
   else if ((pos.x > N/2) && (pos.y < N/2)){
-    gl_FragColor = SECONDARY;
+    fragColor = SECONDARY;
   }
 }
diff --git a/res/shaders/lighting.frag b/res/shaders/lighting.frag
index 2b640ad..d413e38 100644
--- a/res/shaders/lighting.frag
+++ b/res/shaders/lighting.frag
@@ -1,81 +1,96 @@
 #version 330 core
 
+#define VIEWER_OUT_OF_SIGHT_BRIGHTNESS 0.015
+#define VIEWER_GRADIENT_RADIUS         450
+
 struct Light {
   vec2  position;
   vec3  color;
-  float size;
+  float radius;
 };
 
 in vec2 fragTexCoord;
 
-out vec4 finalColor;
+out vec4 fragColor;
 
+// data
+uniform Light lights[128];
+uniform int uLightsAmount;
 uniform sampler2D uOcclusionMask;
 uniform float uTime;
-uniform int uLightsAmount;
 uniform vec2 uResolution;
 uniform vec2 uPlayerLocation;
 
-uniform int uApple;
-uniform int uSmoothShadows;
-uniform int uCascadeAmount;
+// config
+uniform int uCascadeAmount; // the amount of cascades primarily affects performance & light leakage. Thicker walls = less cascades needed, thinner walls = more cascades needed
 uniform int uViewing;
 
-uniform Light lights[64];
-
 // sourced from https://gist.github.com/companje/29408948f1e8be54dd5733a74ca49bb9
 float map(float value, float min1, float max1, float min2, float max2) {
   return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
 }
 
 // this technique is sourced from https://www.shadertoy.com/view/tddXzj
-float terrain(vec2 p, vec2 normalisedLightPos, int smoothShadows)
+float terrain(vec2 p, vec2 position, bool smoothShadows)
 {
-  if (smoothShadows == 1) {
-    if (uApple == 0) return mix(map(distance(uResolution * normalisedLightPos, gl_FragCoord.xy), 0.0, uResolution.x, 0.9, 1.0), 1.0, step(0.25, texture(uOcclusionMask, p).x));
+  if (smoothShadows) {
+    // increased opacity closer to the light source
+    return mix(
+      map(
+        distance(position, gl_FragCoord.xy),
+        0.0,
+        ((uResolution.x < uResolution.y) ? uResolution.y : uResolution.x) * 500,
+        0.9,
+        1.0
+      ),
+      1.0,
+      step(0.25, texture(uOcclusionMask, p).x)
+    );
   }
   return step(0.25, texture(uOcclusionMask, p).x); // hard shadows
 }
 
-void main() {
-  vec3 result = vec3(0.0);
-
-  for (int i = 0; i < uLightsAmount; i++) {
-    vec2 normalisedLightPos = lights[i].position/uResolution;
-    float brightness = 1.0;
-
-    for (float j = 0.0; j < uCascadeAmount; j++) {
-      float t = j / uCascadeAmount;
-      float h = terrain(mix(fragTexCoord, normalisedLightPos, t), normalisedLightPos, uSmoothShadows);
-      brightness *= h;
-    }
+vec3 calcVisibility(vec2 position, float gradientRadius = 300, vec3 rgb = vec3(1.0), bool smoothShadows = true) {
+  // no need to bother calculating light if the light value will be overridden by the gradient anyway
+  if (distance(fragTexCoord*uResolution, position) > gradientRadius) return vec3(0);
 
-    // radial gradient - adapted from https://www.shadertoy.com/view/4tjSWh
+  float brightness = 1.0;
 
-    if (uApple == 1) {
-      brightness *= 1.0 - smoothstep(0.0, 0.5, length(fragTexCoord - normalisedLightPos));
-    } else {
-      brightness *= 1.0 - distance(uResolution.xy * vec2(normalisedLightPos.x, 1.0 - normalisedLightPos.y), gl_FragCoord.xy) * 2/lights[i].size;
-    }
+  vec2 normalisedPos = position/uResolution;
 
-    if (brightness > 0) result += brightness * lights[i].color;
+  for (float j = 0.0; j < uCascadeAmount; j++) {
+    float t = j / uCascadeAmount;
+    float h = terrain(mix(fragTexCoord, normalisedPos, t), position, smoothShadows);
+    brightness *= h;
   }
 
-  if (uViewing == 1) {
-    vec2 normalisedPlayerPos = uPlayerLocation/uResolution;
-    vec3 visible = vec3(1.0);
+  // radial gradient - adapted from https://www.shadertoy.com/view/4tjSWh
+  brightness *= 1.0 - distance(vec2(position.x, uResolution.y - position.y), gl_FragCoord.xy) * 1/gradientRadius;
+
+  return (brightness > 0) ? brightness * rgb : vec3(0);
+}
 
-    for (float j = 0.0; j < uCascadeAmount; j++) {
-      float t = j / uCascadeAmount;
-      float h = terrain(mix(fragTexCoord, normalisedPlayerPos, t), normalisedPlayerPos, 0);
-      visible *= h;
-    }
+void main() {
+  vec3 result = vec3(0.0);
 
-    visible = mix(texture(uOcclusionMask, fragTexCoord).xyz * 0.015, result.xyz, visible);
+  for (int i = 0; i < uLightsAmount; i++) {
+    result += calcVisibility(lights[i].position, lights[i].radius, lights[i].color);
+  }
 
-    gl_FragColor = vec4(visible, 1.0f);
+  if (uViewing == 1) {
+    fragColor = vec4(
+      mix(
+        texture(uOcclusionMask, fragTexCoord).xyz * VIEWER_OUT_OF_SIGHT_BRIGHTNESS,
+        result.xyz,
+        calcVisibility(uPlayerLocation, VIEWER_GRADIENT_RADIUS, vec3(1.0), false)
+      ),
+      1.0
+    );
   } else {
     // combine light result w/ underlying occlusion mask texture
-    gl_FragColor = vec4(result * step(0.25, texture(uOcclusionMask, fragTexCoord)).xxx, 1.0f);
+    fragColor = vec4(
+      result * step(0.25, texture(uOcclusionMask, fragTexCoord)).xxx,
+      1.0
+    );
   }
 }
diff --git a/res/brush.png b/res/textures/brush.png
similarity index 100%
rename from res/brush.png
rename to res/textures/brush.png
diff --git a/res/canvas.png b/res/textures/canvas.png
similarity index 100%
rename from res/canvas.png
rename to res/textures/canvas.png
diff --git a/res/cursor.png b/res/textures/cursor.png
similarity index 100%
rename from res/cursor.png
rename to res/textures/cursor.png
diff --git a/res/textures/icons/drawing.png b/res/textures/icons/drawing.png
new file mode 100644
index 0000000000000000000000000000000000000000..8cd2f5971f37e44bd716860169bc6cb2dd0413b9
GIT binary patch
literal 608
zcmV-m0-ybfP)<h;3K|Lk000e1NJLTq000O8000OG1^@s6#1FoU0004mX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iTcsiu1xqR7kfA!+MMWHI6^c+H)C#RSm|Xe=O&XFE
z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0DrT}RI?`msG4PD
zQb{3~UloF{2qA(YL=lmgsn3aG8lL0p9zMR_#d((Zxj)B%QZO0d6NnQ`H!R`};+aiL
z=e$oGW@SksJ|~_q=z_$LT$f#b<6Lss&ojeDHZxBgCKgIvEO#+08!GWMaZFJ)%J=77
zRyc2QR;zW^z9)ZSsGzMZbDicWQdq<iL<o>kM+H?_h|{W(Vj@HPNe};s;}^*#ldA$o
zjs?`9LUR1zfAG6ovp6;BCWVqf?~84Ji~+%2pw+PL?_=9;odAJn;7aTGYfWJGlk`SM
ziyZ-j+rY(jN0aw}%N-#4q)Ue6NCBGuVi9;hqi@Os!?!^Hn%i4@AEysMmbyyc00)P_
zc!{#tJ>K2d-P^xs+Wq|ijo)&yH;)Fy00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=nNJR9T5+^Ig<bY02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{001~iL_t&-(}j;o4gfF+0-65*&-7HIcH<@kB%&tU&NnYu
u0w)-O8~iQ+j0FHXjAm|cUu2`NNYVr(A}8?)$|-OF0000<MNUMnLSTY}?(rP}

literal 0
HcmV?d00001

diff --git a/res/textures/icons/lighting.png b/res/textures/icons/lighting.png
new file mode 100644
index 0000000000000000000000000000000000000000..199857ea2cf6adfa1641e7cc3cd663aa9fcb18e9
GIT binary patch
literal 603
zcmV-h0;K(kP)<h;3K|Lk000e1NJLTq000O8000OG1^@s6#1FoU0004mX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iTcsiu1xqR7kfA!+MMWHI6^c+H)C#RSm|Xe=O&XFE
z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0DrT}RI?`msG4PD
zQb{3~UloF{2qA(YL=lmgsn3aG8lL0p9zMR_#d((Zxj)B%QZO0d6NnQ`H!R`};+aiL
z=e$oGW@SksJ|~_q=z_$LT$f#b<6Lss&ojeDHZxBgCKgIvEO#+08!GWMaZFJ)%J=77
zRyc2QR;zW^z9)ZSsGzMZbDicWQdq<iL<o>kM+H?_h|{W(Vj@HPNe};s;}^*#ldA$o
zjs?`9LUR1zfAG6ovp6;BCWVqf?~84Ji~+%2pw+PL?_=9;odAJn;7aTGYfWJGlk`SM
ziyZ-j+rY(jN0aw}%N-#4q)Ue6NCBGuVi9;hqi@Os!?!^Hn%i4@AEysMmbyyc00)P_
zc!{#tJ>K2d-P^xs+Wq|ijo)&yH;)Fy00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=nNJR9Ss}0W!wM&02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{001*dL_t&-(_>&j0{@|a0UJP;z)%Dez@~@^Ss^1MBO|(Q
pCUlb+85tSzx`CiW@mhfm003nmH`ke>Xs!SN002ovPDHLkV1m0}?&|;m

literal 0
HcmV?d00001

diff --git a/res/textures/icons/viewing.png b/res/textures/icons/viewing.png
new file mode 100644
index 0000000000000000000000000000000000000000..98febe04add1536acc25f9b8f85b3d5de9351fbf
GIT binary patch
literal 598
zcmV-c0;&CpP)<h;3K|Lk000e1NJLTq000O8000OG1^@s6#1FoU0004mX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iTcsiu1xqR7kfA!+MMWHI6^c+H)C#RSm|Xe=O&XFE
z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0DrT}RI?`msG4PD
zQb{3~UloF{2qA(YL=lmgsn3aG8lL0p9zMR_#d((Zxj)B%QZO0d6NnQ`H!R`};+aiL
z=e$oGW@SksJ|~_q=z_$LT$f#b<6Lss&ojeDHZxBgCKgIvEO#+08!GWMaZFJ)%J=77
zRyc2QR;zW^z9)ZSsGzMZbDicWQdq<iL<o>kM+H?_h|{W(Vj@HPNe};s;}^*#ldA$o
zjs?`9LUR1zfAG6ovp6;BCWVqf?~84Ji~+%2pw+PL?_=9;odAJn;7aTGYfWJGlk`SM
ziyZ-j+rY(jN0aw}%N-#4q)Ue6NCBGuVi9;hqi@Os!?!^Hn%i4@AEysMmbyyc00)P_
zc!{#tJ>K2d-P^xs+Wq|ijo)&yH;)Fy00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=nNJRBqF}=)jt3L02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{001sYL_t&-(_>&D02pD6|NsC0N0DG;gv((_qKF|&{)Yl&
kJ`)2m0G}1aIT9ZL0LpA9<^FB#F#rGn07*qoM6N<$g4|{B7ytkO

literal 0
HcmV?d00001

diff --git a/src/config.h.in b/src/config.h.in
index 0ab63c3..08dd400 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -18,3 +18,5 @@
 
 #define SCREEN_WIDTH  800
 #define SCREEN_HEIGHT 600
+
+#define CURSOR_SIZE 0.1
diff --git a/src/game.cpp b/src/game.cpp
index 97aff39..dd49af3 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -10,19 +10,16 @@ void placeLights(std::vector<Light>* lights, int N = 4, float d = 256.0) {
     Light l;
     l.position = Vector2{ SCREEN_WIDTH/2 + std::sin(t) * d, SCREEN_HEIGHT/2 + std::cos(t) * d};
     l.color    = Vector3{ std::sin(t), std::cos(t), 1.0 };
-    l.size     = 600;
+    l.radius   = 300;
     lights->push_back(l);
   }
 }
 
 Game::Game() {
   debug         = false;
-  smoothShadows = true;
   mode          = DRAWING;
-  cascadeAmount = 512;
-  #if __APPLE__
-  cascadeAmount = 128;
-  #endif
+
+  currentToolIcon = LoadTextureFromImage(LoadImage("res/textures/icons/drawing.png"));
 
   lightingShader = LoadShader(0, "res/shaders/lighting.frag");
   if (!IsShaderValid(lightingShader)) {
@@ -31,14 +28,16 @@ Game::Game() {
     lightingShader = LoadShader(0, "res/shaders/broken.frag");
   }
 
-  cursor.img = LoadImage("res/cursor.png");
+  cascadeAmount = 512;
+
+  cursor.img = LoadImage("res/textures/cursor.png");
   cursor.tex = LoadTextureFromImage(cursor.img);
 
-  brush.img = LoadImage("res/brush.png");
+  brush.img = LoadImage("res/textures/brush.png");
   brush.tex = LoadTextureFromImage(brush.img);
   brush.scale = 0.25;
 
-  canvas.img = LoadImage("res/canvas.png");
+  canvas.img = LoadImage("res/textures/canvas.png");
   canvas.tex = LoadTextureFromImage(canvas.img);
 
   placeLights(&lights);
@@ -60,13 +59,6 @@ void Game::update() {
   SetShaderValue(lightingShader, GetShaderLocation(lightingShader, "uLightsAmount"), &lightsAmount, SHADER_UNIFORM_INT);
   SetShaderValue(lightingShader, GetShaderLocation(lightingShader, "uCascadeAmount"), &cascadeAmount, SHADER_UNIFORM_INT);
 
-  int apple = 0;
-  #ifdef __APPLE__
-    apple = 1;
-  #endif
-  SetShaderValue(lightingShader, GetShaderLocation(lightingShader, "uApple"), &apple, SHADER_UNIFORM_INT);
-  SetShaderValue(lightingShader, GetShaderLocation(lightingShader, "uSmoothShadows"), &smoothShadows, SHADER_UNIFORM_INT);
-
   int viewing = 0;
   if (mode == VIEWING) viewing = 1;
   SetShaderValue(lightingShader, GetShaderLocation(lightingShader, "uViewing"), &viewing, SHADER_UNIFORM_INT);
@@ -84,8 +76,7 @@ void Game::render() {
   for (int i = 0; i < lights.size(); i++) {
     SetShaderValue(lightingShader, GetShaderLocation(lightingShader, TextFormat("lights[%i].position", i)), &lights[i].position, SHADER_UNIFORM_VEC2);
     SetShaderValue(lightingShader, GetShaderLocation(lightingShader, TextFormat("lights[%i].color",    i)), &lights[i].color,    SHADER_UNIFORM_VEC3);
-    SetShaderValue(lightingShader, GetShaderLocation(lightingShader, TextFormat("lights[%i].size",     i)), &lights[i].size,     SHADER_UNIFORM_FLOAT);
-    if (debug) DrawCircleLinesV(lights[i].position, lights[i].size/64, GREEN);
+    SetShaderValue(lightingShader, GetShaderLocation(lightingShader, TextFormat("lights[%i].radius",   i)), &lights[i].radius,   SHADER_UNIFORM_FLOAT);
    }
 }
 
@@ -97,7 +88,9 @@ void Game::renderUI() {
   if (mode == LIGHTING)     str = "LIGHTING";
   else if (mode == VIEWING) str = "VIEWING";
 
-  DrawText(str.c_str(), 0, SCREEN_HEIGHT - 8, 1, ColorFromHSV(0.0, 0.0, 1.0 - v));
+  DrawTexture(currentToolIcon, 0, SCREEN_HEIGHT-8, WHITE);
+
+  DrawText(str.c_str(), 12, SCREEN_HEIGHT - 8, 1, ColorFromHSV(0.0, 0.0, 1.0 - v));
 
   switch (mode) {
     case DRAWING:
@@ -109,12 +102,13 @@ void Game::renderUI() {
                     Color{ 0, 0, 0, 64} );
       break;
     case LIGHTING:
-      DrawCircleLines(GetMouseX(), GetMouseY(), (brush.scale*1200)/64, ColorFromNormalized(Vector4{ std::sin(time), std::cos(time), 1.0, 0.5 }));
+      DrawCircleLines(GetMouseX(), GetMouseY(), (brush.scale*600*2)/64, ColorFromNormalized(Vector4{ std::sin(time), std::cos(time), 1.0, 0.5 }));
+      for (int i = 0; i < lights.size(); i++) {
+        DrawCircleLinesV(lights[i].position, lights[i].radius/64*2, GREEN);
+       }
       break;
   }
 
-  #define CURSOR_SIZE 0.1
-
   DrawTextureEx(cursor.tex,
                 Vector2{ (float)(GetMouseX() - cursor.img.width/2*CURSOR_SIZE),
                          (float)(GetMouseY() - cursor.img.height/2*CURSOR_SIZE) },
@@ -127,17 +121,22 @@ void Game::renderUI() {
   DrawText(TextFormat("%i FPS",            GetFPS()),              0, 0,  1, GREEN);
   DrawText(TextFormat("%i lights",         lights.size()),         0, 8,  1, GREEN);
 
-  if      (mode == DRAWING)  DrawText(TextFormat("%f brush scale", brush.scale),    0, 32, 1, GREEN);
-  else if (mode == LIGHTING) DrawText(TextFormat("%f light size",  brush.scale * 2400), 0, 32, 1, GREEN);
+  if      (mode == DRAWING)  DrawText(TextFormat("%f brush scale",           brush.scale),           0, 32, 1, GREEN);
+  else if (mode == LIGHTING) DrawText(TextFormat("%f light size (diameter)", brush.scale * 600 * 2), 0, 32, 1, GREEN);
 
   DrawText(TextFormat("%i cascades", cascadeAmount), 0, 40, 1, GREEN);
-  if (smoothShadows) DrawText("smoothShadows ON", 0, 48, 1, GREEN);
 }
 
 void Game::processKeyboardInput() {
   auto changeMode = [this](Mode m) {
     mode = m;
     timeSinceModeSwitch = GetTime();
+    if (m == DRAWING)
+      currentToolIcon = LoadTextureFromImage(LoadImage("res/textures/icons/drawing.png"));
+    else if (m == LIGHTING)
+      currentToolIcon = LoadTextureFromImage(LoadImage("res/textures/icons/lighting.png"));
+    else
+      currentToolIcon = LoadTextureFromImage(LoadImage("res/textures/icons/viewing.png"));
   };
 
   if (IsKeyPressed(KEY_ONE))   changeMode(DRAWING);
@@ -150,7 +149,6 @@ void Game::processKeyboardInput() {
     if (!DirectoryExists("screenshots")) MakeDirectory("screenshots");
     TakeScreenshot("screenshots/screenshot.png");
   }
-  if (IsKeyPressed(KEY_S))   (smoothShadows == 0) ? smoothShadows = 1 : smoothShadows = 0;
 
   // clearing
   if (IsKeyPressed(KEY_C)) {
@@ -169,9 +167,18 @@ void Game::processKeyboardInput() {
 
   // replacing
   if (IsKeyPressed(KEY_R)) {
-    if (mode == DRAWING) {
+    if (IsKeyDown(KEY_LEFT_CONTROL)) {
+      // reloading
+        printf("Reloading shaders.\n");
+        UnloadShader(lightingShader);
+        lightingShader = LoadShader(0, "res/shaders/lighting.frag");
+        if (!IsShaderValid(lightingShader)) {
+          UnloadShader(lightingShader);
+          lightingShader = LoadShader(0, "res/shaders/broken.frag");
+        }
+    } else if (mode == DRAWING) {
       printf("Replacing canvas.\n");
-      canvas.img = LoadImage("res/canvas.png");
+      canvas.img = LoadImage("res/textures/canvas.png");
       RELOAD_CANVAS();
     } else if (mode == LIGHTING) {
       printf("Replacing lights.\n");
@@ -179,19 +186,6 @@ void Game::processKeyboardInput() {
       placeLights(&lights);
     }
   }
-
-  if (IsKeyDown(KEY_LEFT_CONTROL)) {
-    // reloading
-    if (IsKeyPressed(KEY_R)) {
-      printf("Reloading shaders.\n");
-      UnloadShader(lightingShader);
-      lightingShader = LoadShader(0, "res/shaders/lighting.frag");
-      if (!IsShaderValid(lightingShader)) {
-        UnloadShader(lightingShader);
-        lightingShader = LoadShader(0, "res/shaders/broken.frag");
-      }
-    }
-  }
 }
 
 void Game::processMouseInput() {
@@ -219,9 +213,9 @@ void Game::processMouseInput() {
         ImageDraw(&canvas.img,
                   brush.img,
                   Rectangle{ 0, 0, (float)canvas.img.width, (float)canvas.img.height },
-                  Rectangle{ static_cast<float>(GetMouseX() - brush.img.width/2 * brush.scale),
+                  Rectangle{ static_cast<float>(GetMouseX() - brush.img.width/2  * brush.scale),
                              static_cast<float>(GetMouseY() - brush.img.height/2 * brush.scale),
-                             static_cast<float>(brush.img.width * brush.scale),
+                             static_cast<float>(brush.img.width  * brush.scale),
                              static_cast<float>(brush.img.height * brush.scale) },
                   BLACK);
         RELOAD_CANVAS();
@@ -229,9 +223,9 @@ void Game::processMouseInput() {
         ImageDraw(&canvas.img,
                   brush.img,
                   Rectangle{ 0, 0, (float)canvas.img.width, (float)canvas.img.height },
-                  Rectangle{ static_cast<float>(GetMouseX() - brush.img.width/2 * brush.scale),
+                  Rectangle{ static_cast<float>(GetMouseX() - brush.img.width/2  * brush.scale),
                              static_cast<float>(GetMouseY() - brush.img.height/2 * brush.scale),
-                             static_cast<float>(brush.img.width * brush.scale),
+                             static_cast<float>(brush.img.width  * brush.scale),
                              static_cast<float>(brush.img.height * brush.scale) },
                   WHITE);
         RELOAD_CANVAS();
@@ -242,7 +236,7 @@ void Game::processMouseInput() {
         Light l;
         l.position = Vector2{ static_cast<float>(GetMouseX()), static_cast<float>(GetMouseY()) };
         l.color    = Vector3{ std::sin(time), std::cos(time), 1.0 };
-        l.size     = brush.scale * 1200;
+        l.radius   = brush.scale * 600;
         lights.push_back(l);
       } else if (IsMouseButtonDown(1)) {
         for (int i = 0; i < lights.size(); i++) {
diff --git a/src/game.h b/src/game.h
index 4f35038..4ddaad8 100644
--- a/src/game.h
+++ b/src/game.h
@@ -11,7 +11,7 @@
 struct Light {
   Vector2 position;
   Vector3 color;
-  float   size; // in pixels
+  float   radius; // in pixels
 };
 
 class Game {
@@ -25,13 +25,14 @@ class Game {
 
   private:
     Shader lightingShader;
-    int smoothShadows;
     int cascadeAmount;
 
     bool debug;
     float time;
     double timeSinceModeSwitch;
 
+    Texture2D currentToolIcon;
+
     std::vector<Light> lights;
 
     enum Mode {
diff --git a/src/main.cpp b/src/main.cpp
index 2a18daa..1d1c5de 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -14,7 +14,7 @@ int main() {
 
   InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, title.c_str());
   SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
-  SetTraceLogLevel(LOG_ERROR);
+  SetTraceLogLevel(LOG_WARNING);
 
   Game game;
 
-- 
GitLab