From 8653dfcac75b29079862f2e7d7a262236f004282 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Fri, 27 Jan 2023 12:00:57 +0100 Subject: [PATCH 01/35] Add xdg directory for linux desktop file and app icons. --- xdg/README.txt | 23 +++++++++++++ xdg/fstlapp-fstl.desktop | 9 +++++ xdg/icons/fstlapp-fstl_128x128.png | Bin 0 -> 8324 bytes xdg/icons/fstlapp-fstl_16x16.png | Bin 0 -> 1122 bytes xdg/icons/fstlapp-fstl_22x22.png | Bin 0 -> 1461 bytes xdg/icons/fstlapp-fstl_256x256.png | Bin 0 -> 18244 bytes xdg/icons/fstlapp-fstl_32x32.png | Bin 0 -> 1991 bytes xdg/icons/fstlapp-fstl_48x48.png | Bin 0 -> 3374 bytes xdg/icons/fstlapp-fstl_64x64.png | Bin 0 -> 4006 bytes xdg/xdg_install.sh | 52 ++++++++++++++++++++++++++++ xdg/xdg_package_install.sh | 53 +++++++++++++++++++++++++++++ xdg/xdg_uninstall.sh | 50 +++++++++++++++++++++++++++ 12 files changed, 187 insertions(+) create mode 100644 xdg/README.txt create mode 100644 xdg/fstlapp-fstl.desktop create mode 100644 xdg/icons/fstlapp-fstl_128x128.png create mode 100644 xdg/icons/fstlapp-fstl_16x16.png create mode 100644 xdg/icons/fstlapp-fstl_22x22.png create mode 100644 xdg/icons/fstlapp-fstl_256x256.png create mode 100644 xdg/icons/fstlapp-fstl_32x32.png create mode 100644 xdg/icons/fstlapp-fstl_48x48.png create mode 100644 xdg/icons/fstlapp-fstl_64x64.png create mode 100755 xdg/xdg_install.sh create mode 100755 xdg/xdg_package_install.sh create mode 100755 xdg/xdg_uninstall.sh diff --git a/xdg/README.txt b/xdg/README.txt new file mode 100644 index 00000000..7aad2b0f --- /dev/null +++ b/xdg/README.txt @@ -0,0 +1,23 @@ +Linux : +----------- +desktop file and application icons installation. +This tells the system that fstl knows to open stl files and allow stl to +be launched using windows key. + +Install : +./xdg_install.sh fstl + +Uninstall : +./xdg_uninstall.sh fstl + +if runned as regular user this will install locally in : + $HOME/.local/share/mime/ + $HOME/.local/share/applications/ + $HOME/.local/share/icons/ + +if runned as root this will install system-wide in : + /usr/share/mime + /usr/share/applications + /usr/share/icons + +Third script xdg_package_install.sh is to be used when building deb or rpm package. diff --git a/xdg/fstlapp-fstl.desktop b/xdg/fstlapp-fstl.desktop new file mode 100644 index 00000000..b4e93c5b --- /dev/null +++ b/xdg/fstlapp-fstl.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=fstl +GenericName=Fast STL Viewer +Exec=fstl %U +Terminal=false +Icon=fstlapp-fstl +Type=Application +MimeType=model/stl; +Categories=Utility; diff --git a/xdg/icons/fstlapp-fstl_128x128.png b/xdg/icons/fstlapp-fstl_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..0c312003be16819219c8b9af1f3e1b646f4cfb43 GIT binary patch literal 8324 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 P00000NkvXXu0mjf18unx literal 0 HcmV?d00001 diff --git a/xdg/icons/fstlapp-fstl_256x256.png b/xdg/icons/fstlapp-fstl_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..4235dc581eb675021d24d062b50e91ec7bee873a GIT binary patch literal 18244 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 Ef`QpW1poj5 literal 0 HcmV?d00001 diff --git a/xdg/icons/fstlapp-fstl_64x64.png b/xdg/icons/fstlapp-fstl_64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..0f83e6bf4c2862628b92c4f42284630db36649d8 GIT binary patch literal 4006 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 M07*qoM6N<$f?1hrZ2$lO literal 0 HcmV?d00001 diff --git a/xdg/xdg_install.sh b/xdg/xdg_install.sh new file mode 100755 index 00000000..b9d59b20 --- /dev/null +++ b/xdg/xdg_install.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# This script will install mimetypes, icons and desktop file, +# it takes a name in argument +# +# if runned as regular user this will install locally in : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will install system-wide in : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide an application name" + exit 1 +fi + +name=$1 + +# echo "Installing mimetypes" +# xdg-mime install fstlapp-$name-mimetypes.xml + +echo "Installing desktop file" +xdg-desktop-menu install fstlapp-$name.desktop + +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource install --theme hicolor --context apps --size 16 icons/${im}_16x16.png $im + xdg-icon-resource install --theme hicolor --context apps --size 22 icons/${im}_22x22.png $im + xdg-icon-resource install --theme hicolor --context apps --size 32 icons/${im}_32x32.png $im + xdg-icon-resource install --theme hicolor --context apps --size 48 icons/${im}_48x48.png $im + xdg-icon-resource install --theme hicolor --context apps --size 64 icons/${im}_64x64.png $im + xdg-icon-resource install --theme hicolor --context apps --size 128 icons/${im}_128x128.png $im + xdg-icon-resource install --theme hicolor --context apps --size 256 icons/${im}_256x256.png $im +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource install --theme hicolor --context mimetypes --size 16 icons/${im}_16x16.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 22 icons/${im}_22x22.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 32 icons/${im}_32x32.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 48 icons/${im}_48x48.png $im +# xdg-icon-resource install --theme hicolor --context mimetypes --size 64 icons/${im}_64x64.png $im +# done + diff --git a/xdg/xdg_package_install.sh b/xdg/xdg_package_install.sh new file mode 100755 index 00000000..7e19a420 --- /dev/null +++ b/xdg/xdg_package_install.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# For a package installation (rpm or deb), we must proceed a different way +# This script takes two arguments, the first one is the installation +# prefix and the second is the name + +if [ $# != 2 ]; then + echo "You must provide two arguments" + exit 1 +fi + +base=$1 +name=$2 + +# echo "Drop mimetypes file in /usr/share/mime/packages/" +# mkdir -p $base/usr/share/mime/packages/ +# cp fstlapp-$name-mimetypes.xml $base/usr/share/mime/packages/ + +echo "Drop desktop file in /usr/share/applications/" +mkdir -p $base/usr/share/applications/ +cp fstlapp-$name.desktop $base/usr/share/applications/ + +slist="16 22 32 48 64 128 256" +echo "Installing apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + for s in $slist + do + mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/apps + cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/apps/$im.png + done +done + +# echo "Installing mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# for s in $slist +# do +# mkdir -p $base/usr/share/icons/hicolor/${s}x${s}/mimetypes +# cp icons/${im}_${s}x${s}.png $base/usr/share/icons/hicolor/${s}x${s}/mimetypes/$im.png +# done +# done + +# +# Put this in the post installation and post uninstallation scripts +# +#echo "Updating mime database" +#update-mime-database /usr/share/mim +# +#echo "Updating desktop database" +#update-desktop-database diff --git a/xdg/xdg_uninstall.sh b/xdg/xdg_uninstall.sh new file mode 100755 index 00000000..1dd7ef4f --- /dev/null +++ b/xdg/xdg_uninstall.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# This script will uninstall mimetypes, icons and desktop file +# +# if runned as regular user this will uninstall locally from : +# $HOME/.local/share/mime/ +# $HOME/.local/share/applications/ +# $HOME/.local/share/icons/ +# +# if runned as root this will uninstall system-wide from : +# /usr/share/mime +# /usr/share/applications +# /usr/share/icons + +if [ $# != 1 ]; then + echo "You must provide a name" + exit 1 +fi + +name=$1 + +# echo "Uninstalling mimetypes" +# xdg-mime uninstall fstlapp-$name-mimetypes.xml + +echo "Uninstalling desktop file" +xdg-desktop-menu uninstall fstlapp-$name.desktop + +echo "Uninstalling apps icons" +iclist="fstlapp-$name" +for im in $iclist +do + xdg-icon-resource uninstall --theme hicolor --context apps --size 16 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 22 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 32 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 48 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 64 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 128 $im + xdg-icon-resource uninstall --theme hicolor --context apps --size 256 $im +done + +# echo "Uninstalling mimetypes icons" +# iclist="`cat fstlapp-$name-mimetypes.xml | grep "icon name" | sed 's/^.*"\(.*\)".*$/\1/'`" +# for im in $iclist +# do +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 16 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 22 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 32 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 48 $im +# xdg-icon-resource uninstall --theme hicolor --context mimetypes --size 64 $im +# done From c92fbe09ca21f857b1fce5050a03fa11adc2857c Mon Sep 17 00:00:00 2001 From: William Daniau Date: Fri, 27 Jan 2023 20:25:23 +0100 Subject: [PATCH 02/35] Add a draw mode with a light source on top right --- gl/gl.qrc | 1 + gl/mesh_light.frag | 29 +++++++++++++++++++++++++++++ src/canvas.cpp | 9 ++++++++- src/canvas.h | 3 ++- src/window.cpp | 12 +++++++++--- src/window.h | 1 + 6 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 gl/mesh_light.frag diff --git a/gl/gl.qrc b/gl/gl.qrc index 6e8baa90..a8065307 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -4,6 +4,7 @@ mesh.vert mesh_wireframe.frag mesh_surfaceangle.frag + mesh_light.frag quad.frag quad.vert colored_lines.frag diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag new file mode 100644 index 00000000..00f4e479 --- /dev/null +++ b/gl/mesh_light.frag @@ -0,0 +1,29 @@ +#version 120 + +uniform float zoom; + +varying vec3 ec_pos; + +void main() { + // Light direction + vec3 dir = vec3(-1,-1,0); + dir = normalize(dir); + // Light color + vec3 color = vec3(1.0, 1.0, 1.0); + + // normal vector + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + float lightcoeff = 0.5 * dot(ec_normal,dir); + float diffusecoeff = 0.5; + gl_FragColor = vec4((diffusecoeff + lightcoeff) * color, 1.0); +} + +// dir 1,0,0 eclairage à gauche +// dir -1,0,0 eclairage à droite +// dir 0,1,0 eclairage en dessous +// dir 0,-1,0 eclairage par dessus +// dir 0,0,1 eclairage par devant +// dir 0,0,-1 eclairage par derriere diff --git a/src/canvas.cpp b/src/canvas.cpp index e3056848..86ce2e05 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -139,6 +139,9 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShader(mesh_vertshader); mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); + mesh_meshlight_shader.addShader(mesh_vertshader); + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + mesh_meshlight_shader.link(); backdrop = new Backdrop(); axis = new Axis(); @@ -176,10 +179,14 @@ void Canvas::draw_mesh() { selected_mesh_shader = &mesh_shader; } - else + else if (drawMode == surfaceangle) { selected_mesh_shader = &mesh_surfaceangle_shader; } + else if (drawMode == meshlight) + { + selected_mesh_shader = &mesh_meshlight_shader; + } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } diff --git a/src/canvas.h b/src/canvas.h index 815fb190..16978bce 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -10,7 +10,7 @@ class Mesh; class Backdrop; class Axis; -enum DrawMode {shaded, wireframe, surfaceangle, DRAWMODECOUNT}; +enum DrawMode {shaded, wireframe, surfaceangle, meshlight, DRAWMODECOUNT}; class Canvas : public QOpenGLWidget, protected QOpenGLFunctions { @@ -61,6 +61,7 @@ public slots: QOpenGLShaderProgram mesh_shader; QOpenGLShaderProgram mesh_wireframe_shader; QOpenGLShaderProgram mesh_surfaceangle_shader; + QOpenGLShaderProgram mesh_meshlight_shader; GLMesh* mesh; Backdrop* backdrop; diff --git a/src/window.cpp b/src/window.cpp index 002f790a..43cee27d 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -22,6 +22,7 @@ Window::Window(QWidget *parent) : shaded_action(new QAction("Shaded", this)), wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), + meshlight_action(new QAction("Light Source from Top Right", this)), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), @@ -111,8 +112,9 @@ Window::Window(QWidget *parent) : draw_menu->addAction(shaded_action); draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); + draw_menu->addAction(meshlight_action); auto drawModes = new QActionGroup(draw_menu); - for (auto p : {shaded_action, wireframe_action, surfaceangle_action}) + for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { drawModes->addAction(p); p->setCheckable(true); @@ -171,7 +173,7 @@ void Window::load_persist_settings(){ draw_mode = shaded; } canvas->set_drawMode(draw_mode); - QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action}; + QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); resize(600, 400); @@ -279,10 +281,14 @@ void Window::on_drawMode(QAction* act) { mode = wireframe; } - else + else if (act == surfaceangle_action) { mode = surfaceangle; } + else if (act == meshlight_action) + { + mode = meshlight; + } canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); } diff --git a/src/window.h b/src/window.h index 81cd29f5..9c5874ac 100644 --- a/src/window.h +++ b/src/window.h @@ -65,6 +65,7 @@ private slots: QAction* const shaded_action; QAction* const wireframe_action; QAction* const surfaceangle_action; + QAction* const meshlight_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; From 97468c8f75b575ed405d519c8a37cfbe0ee800ec Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sun, 29 Jan 2023 00:37:31 +0100 Subject: [PATCH 03/35] Added parameters to the shading program that are then send by Canvas class. --- gl/mesh_light.frag | 27 +++++++++++-------------- src/canvas.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++ src/window.cpp | 2 +- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag index 00f4e479..31d43bb7 100644 --- a/gl/mesh_light.frag +++ b/gl/mesh_light.frag @@ -1,29 +1,26 @@ #version 120 uniform float zoom; +uniform vec4 ambient_light_color; +uniform vec4 directive_light_color; +uniform vec3 directive_light_direction; varying vec3 ec_pos; void main() { - // Light direction - vec3 dir = vec3(-1,-1,0); - dir = normalize(dir); - // Light color - vec3 color = vec3(1.0, 1.0, 1.0); + // Normalize light direction + vec3 dir = normalize(directive_light_direction); + // vec3 a = vec3(0.0, 1.0, 1.0); // normal vector vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); ec_normal.z *= zoom; ec_normal = normalize(ec_normal); - float lightcoeff = 0.5 * dot(ec_normal,dir); - float diffusecoeff = 0.5; - gl_FragColor = vec4((diffusecoeff + lightcoeff) * color, 1.0); -} -// dir 1,0,0 eclairage à gauche -// dir -1,0,0 eclairage à droite -// dir 0,1,0 eclairage en dessous -// dir 0,-1,0 eclairage par dessus -// dir 0,0,1 eclairage par devant -// dir 0,0,-1 eclairage par derriere + vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; + + // float coef = dot(ec_normal,dir); + // vec3 color = coef * lightcolor + (1.0 - coef) * objectcolor; + gl_FragColor = vec4(color, 1.0); +} diff --git a/src/canvas.cpp b/src/canvas.cpp index 86ce2e05..4587389b 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -203,6 +203,56 @@ void Canvas::draw_mesh() // Compensate for z-flattening when zooming glUniform1f(selected_mesh_shader->uniformLocation("zoom"), 1/zoom); + // specific meshlight arguments + if (drawMode == meshlight) { + // Ambient Light Color, followed by the ambient light coefficient to use + glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + // Directive Light Color, followed by the directive light coefficient to use + glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + // + // purely empiric and subjective contrast rule + // standard contrast : + // coefficient_ambient * ||ambient_color|| ~ coefficient_directive * ||directive_color|| + // high contrast : + // coefficient_ambient * ||ambient_color|| < coefficient_directive * ||directive_color|| + // low contrast : + // coefficient_ambient * ||ambient_color|| > coefficient_directive * ||directive_color|| + // + // Examples + // Cool blue high contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.8f); + // 0.7 * 1.30 = 0.91 < 0.8 * 1.732 = 1.38 + // Cool blue standard contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + // 0.67 * 1.30 = 0.87 ~ 0.5 * 1.732 = 0.87 + // Grey standard contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f,1.0f,1.0f,0.5f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + // Grey higher contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f,1.0f,1.0f,0.4f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.6f); + // Grey lower contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f, 1.0f, 1.0f,0.6f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.4f); + // Cyan standard contrast + // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.0f, 1.0f, 1.0f,0.6f); + // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + + // Directive Light Direction + // dir 1,0,0 Light from the left + // dir -1,0,0 Light from the right + // dir 0,1,0 Light from bottom + // dir 0,-1,0 Light from top + // dir 0,0,1 Light from viewer (front) + // dir 0,0,-1 Light from behind + // + // -1,-1,0 Light from top right + glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); + + } + // Find and enable the attribute location for vertex position const GLuint vp = selected_mesh_shader->attributeLocation("vertex_position"); glEnableVertexAttribArray(vp); diff --git a/src/window.cpp b/src/window.cpp index 43cee27d..a0bddfeb 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -22,7 +22,7 @@ Window::Window(QWidget *parent) : shaded_action(new QAction("Shaded", this)), wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), - meshlight_action(new QAction("Light Source from Top Right", this)), + meshlight_action(new QAction("Shaded ambient and directive light source", this)), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), From 9131abc00c57cddbf464acde3e37e44931732a8b Mon Sep 17 00:00:00 2001 From: William Daniau Date: Mon, 30 Jan 2023 11:06:13 +0100 Subject: [PATCH 04/35] Added a preference window for light shader customization. --- CMakeLists.txt | 6 +- src/canvas.cpp | 121 ++++++++++++++++++++++++++- src/canvas.h | 37 +++++++++ src/shaderlightprefs.cpp | 171 +++++++++++++++++++++++++++++++++++++++ src/shaderlightprefs.h | 40 +++++++++ src/window.cpp | 15 ++++ src/window.h | 5 ++ 7 files changed, 389 insertions(+), 6 deletions(-) create mode 100644 src/shaderlightprefs.cpp create mode 100644 src/shaderlightprefs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0628afa4..6f376855 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,8 @@ src/glmesh.cpp src/loader.cpp src/main.cpp src/mesh.cpp -src/window.cpp) +src/window.cpp +src/shaderlightprefs.cpp) #set project headers. set(Project_Headers src/app.h @@ -42,7 +43,8 @@ src/canvas.h src/glmesh.h src/loader.h src/mesh.h -src/window.h) +src/window.h +src/shaderlightprefs.h) #set project resources and icon resource set(Project_Resources qt/qt.qrc gl/gl.qrc) diff --git a/src/canvas.cpp b/src/canvas.cpp index 4587389b..1b7c9bdc 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -11,6 +11,18 @@ const float Canvas::P_PERSPECTIVE = 0.25f; const float Canvas::P_ORTHOGRAPHIC = 0.0f; +const QString Canvas::AMBIENT_COLOR = "ambientColor"; +const QString Canvas::AMBIENT_FACTOR = "ambientFactor"; +const QString Canvas::DIRECTIVE_COLOR = "directiveColor"; +const QString Canvas::DIRECTIVE_FACTOR = "directiveFactor"; +const QString Canvas::CURRENT_LIGHT_DIRECTION = "currentLightDirection"; + +const QColor Canvas::defaultAmbientColor = QColor::fromRgbF(0.22,0.8,1.0); +const QColor Canvas::defaultDirectiveColor = QColor(255,255,255); +const float Canvas::defaultAmbientFactor = 0.67; +const float Canvas::defaultDirectiveFactor = 0.5; +const int Canvas::defaultCurrentLightDirection = 1; + Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) : QOpenGLWidget(parent), mesh(nullptr), scale(1), zoom(1), @@ -24,6 +36,36 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) currentTransform = QMatrix4x4(); currentTransform.setToIdentity(); + QSettings settings; + ambientColor = settings.value(AMBIENT_COLOR,defaultAmbientColor).value(); + directiveColor = settings.value(DIRECTIVE_COLOR,defaultDirectiveColor).value(); + ambientFactor = settings.value(AMBIENT_FACTOR,defaultAmbientFactor).value(); + directiveFactor = settings.value(DIRECTIVE_FACTOR,defaultDirectiveFactor).value(); + + // Fill direction list + // Fill in directions + nameDir.clear(); + listDir.clear(); + QList xname, yname, zname; + xname << "right " << " " << "left "; + yname << "top " << " " << "bottom "; + zname << "rear " << " " << "front "; + for (int i=-1; i<2 ; i++) { + for (int j=-1; j<2; j++) { + for (int k=-1; k<2; k++) { + QString current = xname.at(i+1) + yname.at(j+1) + zname.at(k+1); + if (!(i==0 && j==0 && k==0)) { + nameDir << current.simplified(); + listDir << QVector3D((double)i,(double)j,(double)k); + } + } + } + } + currentLightDirection = settings.value(CURRENT_LIGHT_DIRECTION,defaultCurrentLightDirection).value(); + if (currentLightDirection < 0 || currentLightDirection >= nameDir.length()) { + currentLightDirection = defaultCurrentLightDirection; + } + anim.setDuration(100); } @@ -206,9 +248,11 @@ void Canvas::draw_mesh() // specific meshlight arguments if (drawMode == meshlight) { // Ambient Light Color, followed by the ambient light coefficient to use - glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + //glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); + glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),ambientColor.redF(), ambientColor.greenF(), ambientColor.blueF(), ambientFactor); // Directive Light Color, followed by the directive light coefficient to use - glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + //glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); + glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),directiveColor.redF(),directiveColor.greenF(),directiveColor.blueF(),directiveFactor); // // purely empiric and subjective contrast rule // standard contrast : @@ -249,8 +293,9 @@ void Canvas::draw_mesh() // dir 0,0,-1 Light from behind // // -1,-1,0 Light from top right - glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); - + //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); + + glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); } // Find and enable the attribute location for vertex position @@ -436,3 +481,71 @@ void Canvas::resizeGL(int width, int height) { glViewport(0, 0, width, height); } + +QColor Canvas::getAmbientColor() { + return ambientColor; +} + +void Canvas::setAmbientColor(QColor c) { + ambientColor = c; + QSettings settings; + settings.setValue(AMBIENT_COLOR,c); +} + +double Canvas::getAmbientFactor() { + return (float) ambientFactor; +} + +void Canvas::setAmbientFactor(double f) { + ambientFactor = (float) f; + QSettings settings; + settings.setValue(AMBIENT_FACTOR,f); +} + +void Canvas::resetAmbientColor() { + setAmbientColor(defaultAmbientColor); + setAmbientFactor(defaultAmbientFactor); +} + +QColor Canvas::getDirectiveColor() { + return directiveColor; +} + +void Canvas::setDirectiveColor(QColor c) { + directiveColor = c; + QSettings settings; + settings.setValue(DIRECTIVE_COLOR,c); +} + +double Canvas::getDirectiveFactor() { + return (float) directiveFactor; +} + +void Canvas::setDirectiveFactor(double f) { + directiveFactor = (float) f; + QSettings settings; + settings.setValue(DIRECTIVE_FACTOR,f); +} + +void Canvas::resetDirectiveColor() { + setDirectiveColor(defaultDirectiveColor); + setDirectiveFactor(defaultDirectiveFactor); +} + +QList Canvas::getNameDir() { + return nameDir; +} + +int Canvas::getCurrentLightDirection() { + return currentLightDirection; +} + +void Canvas::setCurrentLightDirection(int ind) { + currentLightDirection = ind; + QSettings settings; + settings.setValue(CURRENT_LIGHT_DIRECTION,currentLightDirection); +} + +void Canvas::resetCurrentLightDirection() { + setCurrentLightDirection(defaultCurrentLightDirection); +} diff --git a/src/canvas.h b/src/canvas.h index 16978bce..8ee24e75 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -28,6 +28,23 @@ class Canvas : public QOpenGLWidget, protected QOpenGLFunctions void invert_zoom(bool d); void set_drawMode(enum DrawMode mode); + QColor getAmbientColor(); + void setAmbientColor(QColor c); + double getAmbientFactor(); + void setAmbientFactor(double f); + void resetAmbientColor(); + + QColor getDirectiveColor(); + void setDirectiveColor(QColor c); + double getDirectiveFactor(); + void setDirectiveFactor(double f); + void resetDirectiveColor(); + + QList getNameDir(); + int getCurrentLightDirection(); + void setCurrentLightDirection(int ind); + void resetCurrentLightDirection(); + public slots: void set_status(const QString& s); void clear_status(); @@ -63,6 +80,26 @@ public slots: QOpenGLShaderProgram mesh_surfaceangle_shader; QOpenGLShaderProgram mesh_meshlight_shader; + QColor ambientColor; + QColor directiveColor; + float ambientFactor; + float directiveFactor; + QList nameDir; + QList listDir; + int currentLightDirection; + + const static QColor defaultAmbientColor; + const static QColor defaultDirectiveColor; + const static float defaultAmbientFactor; + const static float defaultDirectiveFactor; + const static int defaultCurrentLightDirection; + const static QString AMBIENT_COLOR; + const static QString AMBIENT_FACTOR; + const static QString DIRECTIVE_COLOR; + const static QString DIRECTIVE_FACTOR; + const static QString CURRENT_LIGHT_DIRECTION; + + GLMesh* mesh; Backdrop* backdrop; Axis* axis; diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp new file mode 100644 index 00000000..6472c1f0 --- /dev/null +++ b/src/shaderlightprefs.cpp @@ -0,0 +1,171 @@ +#include "shaderlightprefs.h" +#include "canvas.h" +#include + +ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(parent) +{ + canvas = _canvas; + + QVBoxLayout* prefsLayout = new QVBoxLayout; + this->setLayout(prefsLayout); + + QLabel* title = new QLabel("Shader preferences"); + QFont boldFont = QApplication::font(); + boldFont.setWeight(QFont::Bold); + title->setFont(boldFont); + title->setAlignment(Qt::AlignCenter); + prefsLayout->addWidget(title); + + QWidget* middleWidget = new QWidget; + QGridLayout* middleLayout = new QGridLayout; + middleWidget->setLayout(middleLayout); + this->layout()->addWidget(middleWidget); + + // labels + middleLayout->addWidget(new QLabel("Ambient Color"),0,0); + middleLayout->addWidget(new QLabel("Directive Color"),1,0); + middleLayout->addWidget(new QLabel("Direction"),2,0); + + QPixmap dummy(20, 20); + + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor = new QPushButton; + buttonAmbientColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonAmbientColor,0,1); + buttonAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonAmbientColor,SIGNAL(clicked(bool)),this,SLOT(buttonAmbientColorClicked())); + + editAmbientFactor = new QLineEdit; + editAmbientFactor->setValidator(new QDoubleValidator); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + middleLayout->addWidget(editAmbientFactor,0,2); + connect(editAmbientFactor,SIGNAL(editingFinished()),this,SLOT(editAmbientFactorFinished())); + + QPushButton* buttonResetAmbientColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetAmbientColor,0,3); + buttonResetAmbientColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetAmbientColor,SIGNAL(clicked(bool)),this,SLOT(resetAmbientColorClicked())); + + + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor = new QPushButton; + buttonDirectiveColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonDirectiveColor,1,1); + buttonDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(buttonDirectiveColorClicked())); + + editDirectiveFactor = new QLineEdit; + editDirectiveFactor->setValidator(new QDoubleValidator); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + middleLayout->addWidget(editDirectiveFactor,1,2); + connect(editDirectiveFactor,SIGNAL(editingFinished()),this,SLOT(editDirectiveFactorFinished())); + + QPushButton* buttonResetDirectiveColor = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirectiveColor,1,3); + buttonResetDirectiveColor->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirectiveColor,SIGNAL(clicked(bool)),this,SLOT(resetDirectiveColorClicked())); + + // Fill in directions + + comboDirections = new QComboBox; + middleLayout->addWidget(comboDirections,2,1,1,2); + comboDirections->addItems(canvas->getNameDir()); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + connect(comboDirections,SIGNAL(currentIndexChanged(int)),this,SLOT(comboDirectionsChanged(int))); + + QPushButton* buttonResetDirection = new QPushButton("Reset"); + middleLayout->addWidget(buttonResetDirection,2,3); + buttonResetDirection->setFocusPolicy(Qt::NoFocus); + connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); + + + // Hide button + QPushButton* hideButton = new QPushButton("Hide"); + this->layout()->addWidget(hideButton); + hideButton->setFocusPolicy(Qt::NoFocus); + connect(hideButton,SIGNAL(clicked(bool)),this,SLOT(hideButtonClicked())); + +// // OK Cancel +// QWidget* okCancelBox = new QWidget; +// okCancelBox->setLayout(new QHBoxLayout); +// this->layout()->addWidget(okCancelBox); + +// QPushButton *doneButton = new QPushButton("&Ok"); +// okCancelBox->layout()->addWidget(doneButton); +// doneButton->setDefault(true); +// doneButton->setFixedWidth(80); + +// QPushButton *cancelButton = new QPushButton("&Cancel"); +// okCancelBox->layout()->addWidget(cancelButton); +// cancelButton->setFixedWidth(80); + + //connect(doneButton,SIGNAL(clicked(bool)),this,SLOT(doneButtonClicked())); + //connect(cancelButton,SIGNAL(clicked(bool)),this,SLOT(cancelButtonClicked())); +} + +void ShaderLightPrefs::buttonAmbientColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getAmbientColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setAmbientColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editAmbientFactorFinished() { + canvas->setAmbientFactor(editAmbientFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetAmbientColorClicked() { + canvas->resetAmbientColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getAmbientColor()); + buttonAmbientColor->setIcon(QIcon(dummy)); + editAmbientFactor->setText(QString("%1").arg(canvas->getAmbientFactor())); + canvas->update(); +} + +void ShaderLightPrefs::buttonDirectiveColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getDirectiveColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setDirectiveColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::editDirectiveFactorFinished() { + canvas->setDirectiveFactor(editDirectiveFactor->text().toDouble()); + canvas->update(); +} + +void ShaderLightPrefs::resetDirectiveColorClicked() { + canvas->resetDirectiveColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getDirectiveColor()); + buttonDirectiveColor->setIcon(QIcon(dummy)); + editDirectiveFactor->setText(QString("%1").arg(canvas->getDirectiveFactor())); + canvas->update(); +} + +void ShaderLightPrefs::hideButtonClicked() { + this->hide(); +} + +void ShaderLightPrefs::comboDirectionsChanged(int ind) { + canvas->setCurrentLightDirection(ind); + canvas->update(); +} + +void ShaderLightPrefs::resetDirection() { + canvas->resetCurrentLightDirection(); + comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); + canvas->update(); +} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h new file mode 100644 index 00000000..a4e831c6 --- /dev/null +++ b/src/shaderlightprefs.h @@ -0,0 +1,40 @@ +#ifndef SHADERLIGHTPREFS_H +#define SHADERLIGHTPREFS_H + +#include + +class Canvas; +class QLabel; +class QLineEdit; +class QComboBox; + +class ShaderLightPrefs : public QDialog +{ + Q_OBJECT +public: + ShaderLightPrefs(QWidget* parent, Canvas* _canvas); + +private slots: + void buttonAmbientColorClicked(); + void editAmbientFactorFinished(); + void resetAmbientColorClicked(); + + void buttonDirectiveColorClicked(); + void editDirectiveFactorFinished(); + void resetDirectiveColorClicked(); + + void comboDirectionsChanged(int ind); + void resetDirection(); + + void hideButtonClicked(); + +private: + Canvas* canvas; + QPushButton* buttonAmbientColor; + QLineEdit* editAmbientFactor; + QPushButton* buttonDirectiveColor; + QLineEdit* editDirectiveFactor; + QComboBox* comboDirections; +}; + +#endif // SHADERLIGHTPREFS_H diff --git a/src/window.cpp b/src/window.cpp index a0bddfeb..bc415cdb 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,6 +3,7 @@ #include "window.h" #include "canvas.h" #include "loader.h" +#include "shaderlightprefs.h" const QString Window::RECENT_FILE_KEY = "recentFiles"; const QString Window::INVERT_ZOOM_KEY = "invertZoom"; @@ -23,6 +24,7 @@ Window::Window(QWidget *parent) : wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), meshlight_action(new QAction("Shaded ambient and directive light source", this)), + meshlightprefs_action(new QAction(" -> Preferences for the latter.")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), @@ -49,6 +51,10 @@ Window::Window(QWidget *parent) : canvas = new Canvas(format, this); setCentralWidget(canvas); + meshlightprefs = new ShaderLightPrefs(this, canvas); + + QObject::connect(meshlightprefs_action, &QAction::triggered,this,&Window::on_meshlightprefs); + QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); @@ -113,6 +119,7 @@ Window::Window(QWidget *parent) : draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); draw_menu->addAction(meshlight_action); + draw_menu->addAction(meshlightprefs_action); auto drawModes = new QActionGroup(draw_menu); for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { @@ -180,6 +187,14 @@ void Window::load_persist_settings(){ restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); } +void Window::on_meshlightprefs() { + if (meshlightprefs->isVisible()) { + meshlightprefs->hide(); + } else { + meshlightprefs->show(); + } +} + void Window::on_open() { const QString filename = QFileDialog::getOpenFileName( diff --git a/src/window.h b/src/window.h index 9c5874ac..93a5998f 100644 --- a/src/window.h +++ b/src/window.h @@ -7,6 +7,7 @@ #include class Canvas; +class ShaderLightPrefs; class Window : public QMainWindow { @@ -49,6 +50,7 @@ private slots: void on_loaded(const QString& filename); void on_save_screenshot(); void on_hide_menuBar(); + void on_meshlightprefs(); private: void rebuild_recent_files(); @@ -66,6 +68,7 @@ private slots: QAction* const wireframe_action; QAction* const surfaceangle_action; QAction* const meshlight_action; + QAction* const meshlightprefs_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; @@ -92,6 +95,8 @@ private slots: QFileSystemWatcher* watcher; Canvas* canvas; + + ShaderLightPrefs* meshlightprefs; }; #endif // WINDOW_H From 03ea3fc23aab0527657c6050ebc00b7dbb04de79 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Mon, 30 Jan 2023 11:38:54 +0100 Subject: [PATCH 05/35] Save shader preference window position Code cleanup --- src/canvas.cpp | 31 ------------------------------ src/shaderlightprefs.cpp | 41 ++++++++++++++++++++++++---------------- src/shaderlightprefs.h | 7 +++++++ 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 1b7c9bdc..3fe12910 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -253,36 +253,6 @@ void Canvas::draw_mesh() // Directive Light Color, followed by the directive light coefficient to use //glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),directiveColor.redF(),directiveColor.greenF(),directiveColor.blueF(),directiveFactor); - // - // purely empiric and subjective contrast rule - // standard contrast : - // coefficient_ambient * ||ambient_color|| ~ coefficient_directive * ||directive_color|| - // high contrast : - // coefficient_ambient * ||ambient_color|| < coefficient_directive * ||directive_color|| - // low contrast : - // coefficient_ambient * ||ambient_color|| > coefficient_directive * ||directive_color|| - // - // Examples - // Cool blue high contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.8f); - // 0.7 * 1.30 = 0.91 < 0.8 * 1.732 = 1.38 - // Cool blue standard contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.22f, 0.8f, 1.0f, 0.67f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); - // 0.67 * 1.30 = 0.87 ~ 0.5 * 1.732 = 0.87 - // Grey standard contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f,1.0f,1.0f,0.5f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); - // Grey higher contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f,1.0f,1.0f,0.4f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.6f); - // Grey lower contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),1.0f, 1.0f, 1.0f,0.6f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.4f); - // Cyan standard contrast - // glUniform4f(selected_mesh_shader->uniformLocation("ambient_light_color"),0.0f, 1.0f, 1.0f,0.6f); - // glUniform4f(selected_mesh_shader->uniformLocation("directive_light_color"),1.0f,1.0f,1.0f,0.5f); // Directive Light Direction // dir 1,0,0 Light from the left @@ -294,7 +264,6 @@ void Canvas::draw_mesh() // // -1,-1,0 Light from top right //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); - glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); } diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 6472c1f0..5c6d9d4c 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -2,6 +2,8 @@ #include "canvas.h" #include +const QString ShaderLightPrefs::PREFS_GEOM = "shaderPrefsGeometry"; + ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(parent) { canvas = _canvas; @@ -85,22 +87,10 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p hideButton->setFocusPolicy(Qt::NoFocus); connect(hideButton,SIGNAL(clicked(bool)),this,SLOT(hideButtonClicked())); -// // OK Cancel -// QWidget* okCancelBox = new QWidget; -// okCancelBox->setLayout(new QHBoxLayout); -// this->layout()->addWidget(okCancelBox); - -// QPushButton *doneButton = new QPushButton("&Ok"); -// okCancelBox->layout()->addWidget(doneButton); -// doneButton->setDefault(true); -// doneButton->setFixedWidth(80); - -// QPushButton *cancelButton = new QPushButton("&Cancel"); -// okCancelBox->layout()->addWidget(cancelButton); -// cancelButton->setFixedWidth(80); - - //connect(doneButton,SIGNAL(clicked(bool)),this,SLOT(doneButtonClicked())); - //connect(cancelButton,SIGNAL(clicked(bool)),this,SLOT(cancelButtonClicked())); + QSettings settings; + if (!settings.value(PREFS_GEOM).isNull()) { + restoreGeometry(settings.value(PREFS_GEOM).toByteArray()); + } } void ShaderLightPrefs::buttonAmbientColorClicked() { @@ -169,3 +159,22 @@ void ShaderLightPrefs::resetDirection() { comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); canvas->update(); } + +void ShaderLightPrefs::resizeEvent(QResizeEvent *event) +{ + // simply ignore resize +} + +void ShaderLightPrefs::moveEvent(QMoveEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); + QWidget::moveEvent(event); +} + +void ShaderLightPrefs::closeEvent(QCloseEvent *event) +{ + QSettings().setValue(PREFS_GEOM, saveGeometry()); + // replace close event by hide + this->hide(); +} + diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index a4e831c6..dae2adbb 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -14,6 +14,11 @@ class ShaderLightPrefs : public QDialog public: ShaderLightPrefs(QWidget* parent, Canvas* _canvas); +protected: + void resizeEvent(QResizeEvent *event) override; + void moveEvent(QMoveEvent *event) override; + void closeEvent(QCloseEvent *event) override; + private slots: void buttonAmbientColorClicked(); void editAmbientFactorFinished(); @@ -35,6 +40,8 @@ private slots: QPushButton* buttonDirectiveColor; QLineEdit* editDirectiveFactor; QComboBox* comboDirections; + + const static QString PREFS_GEOM; }; #endif // SHADERLIGHTPREFS_H From 8afbaee63a37124d6dd8e109b278d598c63ecd59 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Mon, 30 Jan 2023 13:22:09 +0100 Subject: [PATCH 06/35] Force C locale to force decimal point use in entries. --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 4ec87227..14561ebc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,10 +4,14 @@ int main(int argc, char *argv[]) { + // Force C locale to force decimal point + QLocale::setDefault(QLocale::c()); + QCoreApplication::setOrganizationName("fstl-app"); QCoreApplication::setOrganizationDomain("https://github.com/fstl-app/fstl"); QCoreApplication::setApplicationName("fstl"); QCoreApplication::setApplicationVersion(FSTL_VERSION); App a(argc, argv); + return a.exec(); } From 339e8d9110390742e489388d89635094d134df0f Mon Sep 17 00:00:00 2001 From: William Daniau Date: Tue, 31 Jan 2023 13:35:27 +0100 Subject: [PATCH 07/35] Remove redefinition of closeEvent on ShaderLightPrefs --- src/shaderlightprefs.cpp | 6 ------ src/shaderlightprefs.h | 1 - 2 files changed, 7 deletions(-) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 5c6d9d4c..90f5d280 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -171,10 +171,4 @@ void ShaderLightPrefs::moveEvent(QMoveEvent *event) QWidget::moveEvent(event); } -void ShaderLightPrefs::closeEvent(QCloseEvent *event) -{ - QSettings().setValue(PREFS_GEOM, saveGeometry()); - // replace close event by hide - this->hide(); -} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index dae2adbb..79b4d139 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -17,7 +17,6 @@ class ShaderLightPrefs : public QDialog protected: void resizeEvent(QResizeEvent *event) override; void moveEvent(QMoveEvent *event) override; - void closeEvent(QCloseEvent *event) override; private slots: void buttonAmbientColorClicked(); From 784bac5d2cf6083e57b195160647f149eaa93d27 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Tue, 31 Jan 2023 13:38:04 +0100 Subject: [PATCH 08/35] Save geometry of ShaderLightPrefs on resize --- src/shaderlightprefs.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 90f5d280..184fb7dd 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -162,13 +162,12 @@ void ShaderLightPrefs::resetDirection() { void ShaderLightPrefs::resizeEvent(QResizeEvent *event) { - // simply ignore resize + QSettings().setValue(PREFS_GEOM, saveGeometry()); } void ShaderLightPrefs::moveEvent(QMoveEvent *event) { QSettings().setValue(PREFS_GEOM, saveGeometry()); - QWidget::moveEvent(event); } From be79c504254a86b5eceee532d989060a431498b1 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Tue, 31 Jan 2023 14:00:30 +0100 Subject: [PATCH 09/35] Cosmetic changes on ShaderLightPrefs --- src/shaderlightprefs.cpp | 21 ++++++++++++++------- src/shaderlightprefs.h | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 184fb7dd..d00c7074 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -81,11 +81,18 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); - // Hide button - QPushButton* hideButton = new QPushButton("Hide"); - this->layout()->addWidget(hideButton); - hideButton->setFocusPolicy(Qt::NoFocus); - connect(hideButton,SIGNAL(clicked(bool)),this,SLOT(hideButtonClicked())); + // Ok button + QWidget* boxButton = new QWidget; + QHBoxLayout* boxButtonLayout = new QHBoxLayout; + boxButton->setLayout(boxButtonLayout); + QFrame *spacerL = new QFrame; + spacerL->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); + QPushButton* okButton = new QPushButton("Ok"); + boxButtonLayout->addWidget(spacerL); + boxButtonLayout->addWidget(okButton); + this->layout()->addWidget(boxButton); + okButton->setFocusPolicy(Qt::NoFocus); + connect(okButton,SIGNAL(clicked(bool)),this,SLOT(okButtonClicked())); QSettings settings; if (!settings.value(PREFS_GEOM).isNull()) { @@ -145,8 +152,8 @@ void ShaderLightPrefs::resetDirectiveColorClicked() { canvas->update(); } -void ShaderLightPrefs::hideButtonClicked() { - this->hide(); +void ShaderLightPrefs::okButtonClicked() { + this->close(); } void ShaderLightPrefs::comboDirectionsChanged(int ind) { diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index 79b4d139..9f573399 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -30,7 +30,7 @@ private slots: void comboDirectionsChanged(int ind); void resetDirection(); - void hideButtonClicked(); + void okButtonClicked(); private: Canvas* canvas; From 936e26262a4a3ba4b559b337ab14b385e3a2c7a1 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Wed, 1 Feb 2023 20:59:07 +0100 Subject: [PATCH 10/35] Removed the preferences entry from the Draw menu. Added a "Draw Mode Settings" entry in View menu. It is enabled if settings are available for the current draw mode. --- src/window.cpp | 21 ++++++++++++++++----- src/window.h | 4 ++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 09428ad4..2c512624 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -25,7 +25,7 @@ Window::Window(QWidget *parent) : wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), meshlight_action(new QAction("Shaded ambient and directive light source", this)), - meshlightprefs_action(new QAction(" -> Preferences for the latter.")), + drawModePrefs_action(new QAction("Draw Mode Settings")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), @@ -56,7 +56,7 @@ Window::Window(QWidget *parent) : meshlightprefs = new ShaderLightPrefs(this, canvas); - QObject::connect(meshlightprefs_action, &QAction::triggered,this,&Window::on_meshlightprefs); + QObject::connect(drawModePrefs_action, &QAction::triggered,this,&Window::on_drawModePrefs); QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); @@ -122,7 +122,6 @@ Window::Window(QWidget *parent) : draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); draw_menu->addAction(meshlight_action); - draw_menu->addAction(meshlightprefs_action); auto drawModes = new QActionGroup(draw_menu); for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { @@ -132,6 +131,8 @@ Window::Window(QWidget *parent) : drawModes->setExclusive(true); QObject::connect(drawModes, &QActionGroup::triggered, this, &Window::on_drawMode); + view_menu->addAction(drawModePrefs_action); + drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); QObject::connect(axes_action, &QAction::triggered, @@ -191,15 +192,18 @@ void Window::load_persist_settings(){ { draw_mode = shaded; } - canvas->set_drawMode(draw_mode); QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); + on_drawMode(dm_acts[draw_mode]); resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); } -void Window::on_meshlightprefs() { +void Window::on_drawModePrefs() { + // For now only one draw mode has settings + // when settings for other draw mode will be available + // we will need to check the current mode if (meshlightprefs->isVisible()) { meshlightprefs->hide(); } else { @@ -299,21 +303,28 @@ void Window::on_projection(QAction* proj) void Window::on_drawMode(QAction* act) { + // On mode change hide prefs first + meshlightprefs->hide(); + DrawMode mode; if (act == shaded_action) { + drawModePrefs_action->setEnabled(false); mode = shaded; } else if (act == wireframe_action) { + drawModePrefs_action->setEnabled(false); mode = wireframe; } else if (act == surfaceangle_action) { + drawModePrefs_action->setEnabled(false); mode = surfaceangle; } else if (act == meshlight_action) { + drawModePrefs_action->setEnabled(true); mode = meshlight; } canvas->set_drawMode(mode); diff --git a/src/window.h b/src/window.h index f3957d86..71d8a3f6 100644 --- a/src/window.h +++ b/src/window.h @@ -51,7 +51,7 @@ private slots: void on_loaded(const QString& filename); void on_save_screenshot(); void on_hide_menuBar(); - void on_meshlightprefs(); + void on_drawModePrefs(); private: void rebuild_recent_files(); @@ -69,7 +69,7 @@ private slots: QAction* const wireframe_action; QAction* const surfaceangle_action; QAction* const meshlight_action; - QAction* const meshlightprefs_action; + QAction* const drawModePrefs_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; From 9c6a6c6b215456b1f75fb1109eef6e3d52784c21 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Wed, 1 Feb 2023 20:59:07 +0100 Subject: [PATCH 11/35] Removed the preferences entry from the Draw menu. Added a "Draw Mode Settings" entry in View menu. It is enabled if settings are available for the current draw mode. --- src/window.cpp | 21 ++++++++++++++++----- src/window.h | 4 ++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 09428ad4..2c512624 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -25,7 +25,7 @@ Window::Window(QWidget *parent) : wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), meshlight_action(new QAction("Shaded ambient and directive light source", this)), - meshlightprefs_action(new QAction(" -> Preferences for the latter.")), + drawModePrefs_action(new QAction("Draw Mode Settings")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), reload_action(new QAction("Reload", this)), @@ -56,7 +56,7 @@ Window::Window(QWidget *parent) : meshlightprefs = new ShaderLightPrefs(this, canvas); - QObject::connect(meshlightprefs_action, &QAction::triggered,this,&Window::on_meshlightprefs); + QObject::connect(drawModePrefs_action, &QAction::triggered,this,&Window::on_drawModePrefs); QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); @@ -122,7 +122,6 @@ Window::Window(QWidget *parent) : draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); draw_menu->addAction(meshlight_action); - draw_menu->addAction(meshlightprefs_action); auto drawModes = new QActionGroup(draw_menu); for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { @@ -132,6 +131,8 @@ Window::Window(QWidget *parent) : drawModes->setExclusive(true); QObject::connect(drawModes, &QActionGroup::triggered, this, &Window::on_drawMode); + view_menu->addAction(drawModePrefs_action); + drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); QObject::connect(axes_action, &QAction::triggered, @@ -191,15 +192,18 @@ void Window::load_persist_settings(){ { draw_mode = shaded; } - canvas->set_drawMode(draw_mode); QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); + on_drawMode(dm_acts[draw_mode]); resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); } -void Window::on_meshlightprefs() { +void Window::on_drawModePrefs() { + // For now only one draw mode has settings + // when settings for other draw mode will be available + // we will need to check the current mode if (meshlightprefs->isVisible()) { meshlightprefs->hide(); } else { @@ -299,21 +303,28 @@ void Window::on_projection(QAction* proj) void Window::on_drawMode(QAction* act) { + // On mode change hide prefs first + meshlightprefs->hide(); + DrawMode mode; if (act == shaded_action) { + drawModePrefs_action->setEnabled(false); mode = shaded; } else if (act == wireframe_action) { + drawModePrefs_action->setEnabled(false); mode = wireframe; } else if (act == surfaceangle_action) { + drawModePrefs_action->setEnabled(false); mode = surfaceangle; } else if (act == meshlight_action) { + drawModePrefs_action->setEnabled(true); mode = meshlight; } canvas->set_drawMode(mode); diff --git a/src/window.h b/src/window.h index f3957d86..71d8a3f6 100644 --- a/src/window.h +++ b/src/window.h @@ -51,7 +51,7 @@ private slots: void on_loaded(const QString& filename); void on_save_screenshot(); void on_hide_menuBar(); - void on_meshlightprefs(); + void on_drawModePrefs(); private: void rebuild_recent_files(); @@ -69,7 +69,7 @@ private slots: QAction* const wireframe_action; QAction* const surfaceangle_action; QAction* const meshlight_action; - QAction* const meshlightprefs_action; + QAction* const drawModePrefs_action; QAction* const axes_action; QAction* const invert_zoom_action; QAction* const reload_action; From b61796c87240a4a05f863ef66eed18d750681597 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Wed, 1 Feb 2023 17:19:43 +0100 Subject: [PATCH 12/35] Add fullscreen option in menu associated with key F11 --- src/window.cpp | 17 +++++++++++++++++ src/window.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/window.cpp b/src/window.cpp index 5a7357ed..b4241a71 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -29,6 +29,7 @@ Window::Window(QWidget *parent) : autoreload_action(new QAction("Autoreload", this)), save_screenshot_action(new QAction("Save Screenshot", this)), hide_menuBar_action(new QAction("Hide Menu Bar", this)), + fullscreen_action(new QAction("Toggle Fullscreen",this)), resetTransformOnLoadAction(new QAction("Reset rotation on load",this)), recent_files(new QMenu("Open recent", this)), recent_files_group(new QActionGroup(this)), @@ -145,6 +146,14 @@ Window::Window(QWidget *parent) : this, &Window::on_hide_menuBar); this->addAction(hide_menuBar_action); + view_menu->addAction(fullscreen_action); + fullscreen_action->setShortcut(Qt::Key_F11); + fullscreen_action->setCheckable(true); + QObject::connect(fullscreen_action, &QAction::toggled, + this, &Window::on_fullscreen); + this->addAction(fullscreen_action); + + auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); @@ -627,3 +636,11 @@ void Window::keyPressEvent(QKeyEvent* event) QMainWindow::keyPressEvent(event); } + +void Window::on_fullscreen() { + if (!this->isFullScreen()) { + this->showFullScreen(); + } else { + this->showNormal(); + } +} diff --git a/src/window.h b/src/window.h index 8aa6b747..3efbeadd 100644 --- a/src/window.h +++ b/src/window.h @@ -49,6 +49,7 @@ private slots: void on_load_recent(QAction* a); void on_loaded(const QString& filename); void on_save_screenshot(); + void on_fullscreen(); void on_hide_menuBar(); private: @@ -72,6 +73,7 @@ private slots: QAction* const autoreload_action; QAction* const save_screenshot_action; QAction* const hide_menuBar_action; + QAction* const fullscreen_action; QAction* const resetTransformOnLoadAction; QMenu* const recent_files; From 25a12b86d836857f4d12c2956925f247f5ed071f Mon Sep 17 00:00:00 2001 From: William Daniau Date: Wed, 1 Feb 2023 22:42:09 +0100 Subject: [PATCH 13/35] Change some shortcuts. Add ability to cycle through shaders with up and down keys. Added : A : Toggle Draw axes S : Take Screenshot Up : Use next shader Down : use previous shader Modified : M : Toggle Menu (was Ctrl Shift C) F : Toggle Fullscreen (was F11) --- src/window.cpp | 40 ++++++++++++++++++++++++++++++++-------- src/window.h | 3 +++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 5ac9ade7..13451308 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -90,6 +90,7 @@ Window::Window(QWidget *parent) : this, &Window::on_load_recent); save_screenshot_action->setCheckable(false); + save_screenshot_action->setShortcut(Qt::Key_S); QObject::connect(save_screenshot_action, &QAction::triggered, this, &Window::on_save_screenshot); @@ -136,7 +137,8 @@ Window::Window(QWidget *parent) : drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); - QObject::connect(axes_action, &QAction::triggered, + axes_action->setShortcut(Qt::Key_A); + QObject::connect(axes_action, &QAction::toggled, this, &Window::on_drawAxes); view_menu->addAction(invert_zoom_action); @@ -150,14 +152,14 @@ Window::Window(QWidget *parent) : this, &Window::on_resetTransformOnLoad); view_menu->addAction(hide_menuBar_action); - hide_menuBar_action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); + hide_menuBar_action->setShortcut(Qt::Key_M); hide_menuBar_action->setCheckable(true); QObject::connect(hide_menuBar_action, &QAction::toggled, this, &Window::on_hide_menuBar); this->addAction(hide_menuBar_action); view_menu->addAction(fullscreen_action); - fullscreen_action->setShortcut(Qt::Key_F11); + fullscreen_action->setShortcut(Qt::Key_F); fullscreen_action->setCheckable(true); QObject::connect(fullscreen_action, &QAction::toggled, this, &Window::on_fullscreen); @@ -201,7 +203,7 @@ void Window::load_persist_settings(){ { draw_mode = shaded; } - QAction* (dm_acts[]) = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; + dm_acts = {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}; dm_acts[draw_mode]->setChecked(true); on_drawMode(dm_acts[draw_mode]); @@ -659,10 +661,11 @@ void Window::keyPressEvent(QKeyEvent* event) { load_next(); return; - } - else if (event->key() == Qt::Key_Escape) - { - hide_menuBar_action->setChecked(false); + } else if (event->key() == Qt::Key_Up) { + cycleShader(true); + return; + } else if (event->key() == Qt::Key_Down) { + cycleShader(false); return; } @@ -676,3 +679,24 @@ void Window::on_fullscreen() { this->showNormal(); } } + +int Window::getCurrentShader() { + int shadeNumber = dm_acts.size(); + int current = 0; + for (int i=0; iisChecked()) { + current = i; + break; + } + } + return current; +} + +void Window::cycleShader(bool up) { + int current = getCurrentShader(); + int updown = up ? 1 : -1; + int nextS = (current + updown) % dm_acts.size(); + nextS = nextS < 0 ? dm_acts.size() - 1 : nextS; + dm_acts.at(nextS)->setChecked(true); + on_drawMode(dm_acts.at(nextS)); +} diff --git a/src/window.h b/src/window.h index 22ed51f3..ef8f3d8a 100644 --- a/src/window.h +++ b/src/window.h @@ -17,6 +17,8 @@ class Window : public QMainWindow bool load_stl(const QString& filename, bool is_reload=false); bool load_prev(void); bool load_next(void); + int getCurrentShader(); + void cycleShader(bool); protected: void dragEnterEvent(QDragEnterEvent* event) override; @@ -102,6 +104,7 @@ private slots: Canvas* canvas; ShaderLightPrefs* meshlightprefs; + QList dm_acts; }; #endif // WINDOW_H From 3da47eced292d0310306ecf898574639a4c59240 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Thu, 2 Feb 2023 17:06:03 +0100 Subject: [PATCH 14/35] Regroup shortcuts on top of window.cpp file in static variables Add some action in the window widget so the shortcuts will be available even when the menu bar is hidden. --- src/window.cpp | 33 ++++++++++++++++++++++++++------- src/window.h | 9 +++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 13451308..fee1aff9 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -14,6 +14,16 @@ const QString Window::DRAW_MODE_KEY = "drawMode"; const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; const QString Window::RESET_TRANSFORM_ON_LOAD_KEY = "resetTransformOnLoad"; +const QKeySequence Window::shortcutOpen = Qt::Key_O; +const QKeySequence Window::shortcutReload = Qt::Key_R; +const QKeySequence Window::shortcutScreenshot = Qt::Key_S; +const QKeySequence Window::shortcutQuit = Qt::Key_Q; +const QKeySequence Window::shortcutDrawModeSettings = Qt::Key_P; +const QKeySequence Window::shortcutDrawAxes = Qt::Key_A; +const QKeySequence Window::shortcutHideMenuBar = Qt::Key_M; +const QKeySequence Window::shortcutFullscreen = Qt::Key_F; + + Window::Window(QWidget *parent) : QMainWindow(parent), open_action(new QAction("Open", this)), @@ -62,12 +72,14 @@ Window::Window(QWidget *parent) : QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, &Window::on_watched_change); - open_action->setShortcut(QKeySequence::Open); + //open_action->setShortcut(QKeySequence::Open); + open_action->setShortcut(shortcutOpen); QObject::connect(open_action, &QAction::triggered, this, &Window::on_open); this->addAction(open_action); - quit_action->setShortcut(QKeySequence::Quit); + //quit_action->setShortcut(QKeySequence::Quit); + quit_action->setShortcut(shortcutQuit); QObject::connect(quit_action, &QAction::triggered, this, &Window::close); this->addAction(quit_action); @@ -76,7 +88,9 @@ Window::Window(QWidget *parent) : QObject::connect(autoreload_action, &QAction::triggered, this, &Window::on_autoreload_triggered); - reload_action->setShortcut(QKeySequence::Refresh); + //reload_action->setShortcut(QKeySequence::Refresh); + reload_action->setShortcut(shortcutReload); + this->addAction(reload_action); reload_action->setEnabled(false); QObject::connect(reload_action, &QAction::triggered, this, &Window::on_reload); @@ -90,7 +104,8 @@ Window::Window(QWidget *parent) : this, &Window::on_load_recent); save_screenshot_action->setCheckable(false); - save_screenshot_action->setShortcut(Qt::Key_S); + save_screenshot_action->setShortcut(shortcutScreenshot); + this->addAction(save_screenshot_action); QObject::connect(save_screenshot_action, &QAction::triggered, this, &Window::on_save_screenshot); @@ -134,10 +149,13 @@ Window::Window(QWidget *parent) : QObject::connect(drawModes, &QActionGroup::triggered, this, &Window::on_drawMode); view_menu->addAction(drawModePrefs_action); + drawModePrefs_action->setShortcut(shortcutDrawModeSettings); + this->addAction(drawModePrefs_action); drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); - axes_action->setShortcut(Qt::Key_A); + axes_action->setShortcut(shortcutDrawAxes); + this->addAction(axes_action); QObject::connect(axes_action, &QAction::toggled, this, &Window::on_drawAxes); @@ -152,14 +170,15 @@ Window::Window(QWidget *parent) : this, &Window::on_resetTransformOnLoad); view_menu->addAction(hide_menuBar_action); - hide_menuBar_action->setShortcut(Qt::Key_M); + hide_menuBar_action->setShortcut(shortcutHideMenuBar); hide_menuBar_action->setCheckable(true); QObject::connect(hide_menuBar_action, &QAction::toggled, this, &Window::on_hide_menuBar); + // To have the shortcut work without the menu this->addAction(hide_menuBar_action); view_menu->addAction(fullscreen_action); - fullscreen_action->setShortcut(Qt::Key_F); + fullscreen_action->setShortcut(shortcutFullscreen); fullscreen_action->setCheckable(true); QObject::connect(fullscreen_action, &QAction::toggled, this, &Window::on_fullscreen); diff --git a/src/window.h b/src/window.h index ef8f3d8a..ab15a94d 100644 --- a/src/window.h +++ b/src/window.h @@ -95,6 +95,15 @@ private slots: const static QString WINDOW_GEOM_KEY; const static QString RESET_TRANSFORM_ON_LOAD_KEY; + const static QKeySequence shortcutOpen; + const static QKeySequence shortcutReload; + const static QKeySequence shortcutScreenshot; + const static QKeySequence shortcutQuit; + const static QKeySequence shortcutDrawModeSettings; + const static QKeySequence shortcutDrawAxes; + const static QKeySequence shortcutHideMenuBar; + const static QKeySequence shortcutFullscreen; + QString current_file; QString lookup_folder; QStringList lookup_folder_files; From 5a97d1498c462d5aff319f07e32222bd2647522c Mon Sep 17 00:00:00 2001 From: William Daniau Date: Thu, 2 Feb 2023 20:49:26 +0100 Subject: [PATCH 15/35] Added method to resize window for a given canvas size --- src/window.cpp | 11 ++++++++++- src/window.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/window.cpp b/src/window.cpp index fee1aff9..580d55f2 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -23,7 +23,6 @@ const QKeySequence Window::shortcutDrawAxes = Qt::Key_A; const QKeySequence Window::shortcutHideMenuBar = Qt::Key_M; const QKeySequence Window::shortcutFullscreen = Qt::Key_F; - Window::Window(QWidget *parent) : QMainWindow(parent), open_action(new QAction("Open", this)), @@ -719,3 +718,13 @@ void Window::cycleShader(bool up) { dm_acts.at(nextS)->setChecked(true); on_drawMode(dm_acts.at(nextS)); } + + +// Resize the widget giving the canvas dimension +// Useful for screenshot of given size. +void Window::setCanvasSize(int w, int h) { + this->showNormal(); + int dw = this->size().width() - canvas->size().width(); + int dh = this->size().height() - canvas->size().height(); + this->resize(w + dw, h + dh); +} diff --git a/src/window.h b/src/window.h index ab15a94d..14c68f03 100644 --- a/src/window.h +++ b/src/window.h @@ -19,6 +19,7 @@ class Window : public QMainWindow bool load_next(void); int getCurrentShader(); void cycleShader(bool); + void setCanvasSize(int w, int h); protected: void dragEnterEvent(QDragEnterEvent* event) override; From 8a666f9078d9b6ed5016ab94c9432c6cef7e890e Mon Sep 17 00:00:00 2001 From: William Daniau Date: Thu, 2 Feb 2023 20:50:45 +0100 Subject: [PATCH 16/35] Write canvas size when drawing axes --- src/canvas.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/canvas.cpp b/src/canvas.cpp index 239cbf49..9faaf252 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -211,6 +211,17 @@ void Canvas::paintGL() float textHeight = painter.fontInfo().pointSize(); if (drawAxes) painter.drawText(QRect(10, textHeight, width(), height()), meshInfo); painter.drawText(10, height() - textHeight, status); + + if (drawAxes) { + QString sWidth = QString("GL Width = %1").arg(width()); + QString sHeight = QString("GL Height = %1").arg(height()); + int sWidthLength = painter.fontMetrics().horizontalAdvance(sWidth); + int sHeightLength = painter.fontMetrics().horizontalAdvance(sHeight); + int origin = std::min(sWidthLength,sHeightLength); + painter.drawText(width() - origin - 10, textHeight + 10, sWidth); + painter.drawText(width() - origin - 10, 2* textHeight + 10, sHeight); + } + } void Canvas::draw_mesh() From 990b5e4c7b72399b5fe9f29b025dc15774d9e677 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Fri, 3 Feb 2023 16:06:24 +0100 Subject: [PATCH 17/35] Add some icons --- qt/icons/document-open.png | Bin 0 -> 3696 bytes qt/icons/exit.png | Bin 0 -> 4238 bytes qt/icons/screenshot.png | Bin 0 -> 11467 bytes qt/icons/sphere_shader1.png | Bin 0 -> 4095 bytes qt/icons/sphere_shader2.png | Bin 0 -> 5822 bytes qt/icons/sphere_shader3.png | Bin 0 -> 4565 bytes qt/icons/sphere_shader4.png | Bin 0 -> 4230 bytes qt/icons/view-fullscreen.png | Bin 0 -> 2996 bytes qt/icons/view-refresh.png | Bin 0 -> 5522 bytes qt/qt.qrc | 9 +++++++++ src/window.cpp | 12 +++++++++++- src/window.h | 1 + 12 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 qt/icons/document-open.png create mode 100644 qt/icons/exit.png create mode 100644 qt/icons/screenshot.png create mode 100644 qt/icons/sphere_shader1.png create mode 100644 qt/icons/sphere_shader2.png create mode 100644 qt/icons/sphere_shader3.png create mode 100644 qt/icons/sphere_shader4.png create mode 100644 qt/icons/view-fullscreen.png create mode 100644 qt/icons/view-refresh.png diff --git a/qt/icons/document-open.png b/qt/icons/document-open.png new file mode 100644 index 0000000000000000000000000000000000000000..c94a7edf7a8a078eb7d447b7d7af436ff6f5dfd6 GIT binary patch literal 3696 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 Kl9dw1!T$q%9$qv6 literal 0 HcmV?d00001 diff --git a/qt/icons/sphere_shader1.png b/qt/icons/sphere_shader1.png new file mode 100644 index 0000000000000000000000000000000000000000..84931c37281c6b52ea0ff05c2a314cd2b0ddedf8 GIT binary patch literal 4095 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 IM6N<$g8qjiKmY&$ literal 0 HcmV?d00001 diff --git a/qt/icons/sphere_shader3.png b/qt/icons/sphere_shader3.png new file mode 100644 index 0000000000000000000000000000000000000000..ca05cd456b7f4a699466d8d5777cd0fe3648f632 GIT binary patch literal 4565 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 literal 0 HcmV?d00001 diff --git a/qt/icons/view-fullscreen.png b/qt/icons/view-fullscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0599241e1a5be726de67e02761634ee8c22243 GIT binary patch literal 2996 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 U04aIj?EnA(07*qoM6N<$f_NcW{{R30 literal 0 HcmV?d00001 diff --git a/qt/qt.qrc b/qt/qt.qrc index f2b1266f..4e4a2dff 100644 --- a/qt/qt.qrc +++ b/qt/qt.qrc @@ -2,5 +2,14 @@ style.qss icons/fstl_64x64.png + icons/sphere_shader1.png + icons/sphere_shader2.png + icons/sphere_shader3.png + icons/sphere_shader4.png + icons/document-open.png + icons/exit.png + icons/screenshot.png + icons/view-fullscreen.png + icons/view-refresh.png diff --git a/src/window.cpp b/src/window.cpp index 580d55f2..ee65e57f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -73,12 +73,14 @@ Window::Window(QWidget *parent) : //open_action->setShortcut(QKeySequence::Open); open_action->setShortcut(shortcutOpen); + open_action->setIcon(QIcon(":/qt/icons/document-open.png")); QObject::connect(open_action, &QAction::triggered, this, &Window::on_open); this->addAction(open_action); //quit_action->setShortcut(QKeySequence::Quit); quit_action->setShortcut(shortcutQuit); + quit_action->setIcon(QIcon(":/qt/icons/exit.png")); QObject::connect(quit_action, &QAction::triggered, this, &Window::close); this->addAction(quit_action); @@ -89,6 +91,7 @@ Window::Window(QWidget *parent) : //reload_action->setShortcut(QKeySequence::Refresh); reload_action->setShortcut(shortcutReload); + reload_action->setIcon(QIcon(":/qt/icons/view-refresh.png")); this->addAction(reload_action); reload_action->setEnabled(false); QObject::connect(reload_action, &QAction::triggered, @@ -104,6 +107,7 @@ Window::Window(QWidget *parent) : save_screenshot_action->setCheckable(false); save_screenshot_action->setShortcut(shortcutScreenshot); + save_screenshot_action->setIcon(QIcon(":/qt/icons/screenshot.png")); this->addAction(save_screenshot_action); QObject::connect(save_screenshot_action, &QAction::triggered, this, &Window::on_save_screenshot); @@ -133,11 +137,15 @@ Window::Window(QWidget *parent) : QObject::connect(projections, &QActionGroup::triggered, this, &Window::on_projection); - auto draw_menu = view_menu->addMenu("Draw Mode"); + draw_menu = view_menu->addMenu("Draw Mode"); draw_menu->addAction(shaded_action); draw_menu->addAction(wireframe_action); draw_menu->addAction(surfaceangle_action); draw_menu->addAction(meshlight_action); + shaded_action->setIcon(QIcon(":/qt/icons/sphere_shader1.png")); + wireframe_action->setIcon(QIcon(":/qt/icons/sphere_shader2.png")); + surfaceangle_action->setIcon(QIcon(":/qt/icons/sphere_shader3.png")); + meshlight_action->setIcon(QIcon(":/qt/icons/sphere_shader4.png")); auto drawModes = new QActionGroup(draw_menu); for (auto p : {shaded_action, wireframe_action, surfaceangle_action, meshlight_action}) { @@ -178,6 +186,7 @@ Window::Window(QWidget *parent) : view_menu->addAction(fullscreen_action); fullscreen_action->setShortcut(shortcutFullscreen); + fullscreen_action->setIcon(QIcon(":/qt/icons/view-fullscreen.png")); fullscreen_action->setCheckable(true); QObject::connect(fullscreen_action, &QAction::toggled, this, &Window::on_fullscreen); @@ -358,6 +367,7 @@ void Window::on_drawMode(QAction* act) } canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); + draw_menu->setIcon(act->icon()); } void Window::on_drawAxes(bool d) diff --git a/src/window.h b/src/window.h index 14c68f03..6fcc3d9e 100644 --- a/src/window.h +++ b/src/window.h @@ -84,6 +84,7 @@ private slots: QAction* const resetTransformOnLoadAction; QMenu* const recent_files; + QMenu* draw_menu; QActionGroup* const recent_files_group; QAction* const recent_files_clear_action; const static int MAX_RECENT_FILES=8; From 25d540ac1bd896be1b3ea2faffc3267a1158abd1 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 4 Feb 2023 14:21:10 +0100 Subject: [PATCH 18/35] Add preferences icon --- qt/icons/preferences-system.png | Bin 0 -> 6045 bytes qt/qt.qrc | 1 + 2 files changed, 1 insertion(+) create mode 100644 qt/icons/preferences-system.png diff --git a/qt/icons/preferences-system.png b/qt/icons/preferences-system.png new file mode 100644 index 0000000000000000000000000000000000000000..97c480256d1f51a5939cc1d3f348285cd847d0a4 GIT binary patch literal 6045 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/screenshot.png icons/view-fullscreen.png icons/view-refresh.png + icons/preferences-system.png From 58a300863ee75a52888165deb1a7d86775d17be2 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 4 Feb 2023 20:49:17 +0100 Subject: [PATCH 19/35] Add more icons and a toolbar. The toolbar is toggled with the menu. --- qt/icons/auto_refresh.png | Bin 0 -> 18428 bytes qt/icons/axes.png | Bin 0 -> 10142 bytes qt/icons/invert_zoom.png | Bin 0 -> 16150 bytes qt/icons/orthographic.png | Bin 0 -> 9670 bytes qt/icons/perspective.png | Bin 0 -> 9895 bytes qt/icons/reset_rotation_on_load.png | Bin 0 -> 14357 bytes qt/qt.qrc | 6 +++ src/window.cpp | 73 ++++++++++++++++++++++++++-- src/window.h | 6 +++ 9 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 qt/icons/auto_refresh.png create mode 100644 qt/icons/axes.png create mode 100644 qt/icons/invert_zoom.png create mode 100644 qt/icons/orthographic.png create mode 100644 qt/icons/perspective.png create mode 100644 qt/icons/reset_rotation_on_load.png diff --git a/qt/icons/auto_refresh.png b/qt/icons/auto_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..34826986f2e2aee3fcd2ecf3592564272ed8c079 GIT binary patch literal 18428 zcmeIaWl&t*5-vQry99UF!QI{6T?Th|Cjo+MaCdhJE+HYfOK^902$#Ia>$`tW)p!4$ znc6aIbw8{7>0Yb$+SN6Y%8F7*@Obb5002owT3q%0D*Wewg?>L*SUax)0OaRB>e_Cq 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 J8d2kr{{y`lnMeQt literal 0 HcmV?d00001 diff --git a/qt/icons/axes.png b/qt/icons/axes.png new file mode 100644 index 0000000000000000000000000000000000000000..e44007357a20f1624079e584e2f4e87e6eb23d37 GIT binary patch literal 10142 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 literal 0 HcmV?d00001 diff --git a/qt/icons/perspective.png b/qt/icons/perspective.png new file mode 100644 index 0000000000000000000000000000000000000000..442f9bf07c6ccb66c0d37a692a2b4f9e2ace2478 GIT binary patch literal 9895 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/view-fullscreen.png icons/view-refresh.png icons/preferences-system.png + icons/axes.png + icons/orthographic.png + icons/perspective.png + icons/reset_rotation_on_load.png + icons/invert_zoom.png + icons/auto_refresh.png diff --git a/src/window.cpp b/src/window.cpp index ee65e57f..40c1a64c 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -86,6 +86,7 @@ Window::Window(QWidget *parent) : this->addAction(quit_action); autoreload_action->setCheckable(true); + autoreload_action->setIcon(QIcon(":/qt/icons/auto_refresh.png")); QObject::connect(autoreload_action, &QAction::triggered, this, &Window::on_autoreload_triggered); @@ -124,9 +125,11 @@ Window::Window(QWidget *parent) : file_menu->addAction(quit_action); auto view_menu = menuBar()->addMenu("View"); - auto projection_menu = view_menu->addMenu("Projection"); + projection_menu = view_menu->addMenu("Projection"); projection_menu->addAction(perspective_action); + perspective_action->setIcon(QIcon(":/qt/icons/perspective.png")); projection_menu->addAction(orthographic_action); + orthographic_action->setIcon(QIcon(":/qt/icons/orthographic.png")); auto projections = new QActionGroup(projection_menu); for (auto p : {perspective_action, orthographic_action}) { @@ -157,22 +160,26 @@ Window::Window(QWidget *parent) : this, &Window::on_drawMode); view_menu->addAction(drawModePrefs_action); drawModePrefs_action->setShortcut(shortcutDrawModeSettings); + drawModePrefs_action->setIcon(QIcon(":/qt/icons/preferences-system.png")); this->addAction(drawModePrefs_action); drawModePrefs_action->setDisabled(true); view_menu->addAction(axes_action); axes_action->setCheckable(true); axes_action->setShortcut(shortcutDrawAxes); + axes_action->setIcon(QIcon(":/qt/icons/axes.png")); this->addAction(axes_action); QObject::connect(axes_action, &QAction::toggled, this, &Window::on_drawAxes); view_menu->addAction(invert_zoom_action); invert_zoom_action->setCheckable(true); + invert_zoom_action->setIcon(QIcon(":/qt/icons/invert_zoom.png")); QObject::connect(invert_zoom_action, &QAction::triggered, this, &Window::on_invertZoom); view_menu->addAction(resetTransformOnLoadAction); resetTransformOnLoadAction->setCheckable(true); + resetTransformOnLoadAction->setIcon(QIcon(":/qt/icons/reset_rotation_on_load.png")); QObject::connect(resetTransformOnLoadAction, &QAction::triggered, this, &Window::on_resetTransformOnLoad); @@ -196,6 +203,54 @@ Window::Window(QWidget *parent) : auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); + // Toolbar + // First group + windowToolBar = new QToolBar; + windowToolBar->addAction(quit_action); + windowToolBar->addAction(open_action); + windowToolBar->addAction(reload_action); + windowToolBar->addAction(autoreload_action); + + // preferences button here + windowToolBar->addSeparator(); + + // Second group + projectionButton = new QToolButton; + projectionButton->setPopupMode(QToolButton::InstantPopup); + projectionButton->setMenu(projection_menu); + projectionButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(projectionButton); + + shaderButton = new QToolButton; + shaderButton->setPopupMode(QToolButton::InstantPopup); + shaderButton->setMenu(draw_menu); + shaderButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(shaderButton); + windowToolBar->addAction(drawModePrefs_action); + + windowToolBar->addAction(axes_action); + windowToolBar->addAction(invert_zoom_action); + windowToolBar->addAction(resetTransformOnLoadAction); + + windowToolBar->addSeparator(); + // Third group + windowToolBar->addAction(save_screenshot_action); + windowToolBar->addAction(fullscreen_action); + + + + + // reset view here + // select views here + // slect shader here + // select gl size here + + + + + + + this->addToolBar(windowToolBar); load_persist_settings(); } @@ -216,13 +271,16 @@ void Window::load_persist_settings(){ axes_action->setChecked(draw_axes); QString projection = settings.value(PROJECTION_KEY, "perspective").toString(); + QAction* currentProjection; if(projection == "perspective"){ canvas->view_perspective(Canvas::P_PERSPECTIVE, false); - perspective_action->setChecked(true); + currentProjection = perspective_action; }else{ canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, false); - orthographic_action->setChecked(true); + currentProjection = orthographic_action; } + currentProjection->setChecked(true); + on_projection(currentProjection); DrawMode draw_mode = (DrawMode)settings.value(DRAW_MODE_KEY, DRAWMODECOUNT).toInt(); @@ -337,6 +395,9 @@ void Window::on_projection(QAction* proj) canvas->view_perspective(Canvas::P_ORTHOGRAPHIC, true); QSettings().setValue(PROJECTION_KEY, "orthographic"); } + projection_menu->setIcon(proj->icon()); + projectionButton->setIcon(proj->icon()); + projectionButton->setToolTip(QString("%1 : %2").arg(projection_menu->title()).arg(proj->toolTip())); } void Window::on_drawMode(QAction* act) @@ -368,6 +429,8 @@ void Window::on_drawMode(QAction* act) canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); draw_menu->setIcon(act->icon()); + shaderButton->setIcon(act->icon()); + shaderButton->setToolTip(QString("%1 : %2").arg(draw_menu->title()).arg(act->toolTip())); } void Window::on_drawAxes(bool d) @@ -458,6 +521,7 @@ void Window::on_save_screenshot() void Window::on_hide_menuBar() { menuBar()->setVisible(!hide_menuBar_action->isChecked()); + windowToolBar->setVisible(!hide_menuBar_action->isChecked()); } void Window::rebuild_recent_files() @@ -695,6 +759,9 @@ void Window::keyPressEvent(QKeyEvent* event) } else if (event->key() == Qt::Key_Down) { cycleShader(false); return; + } else if (event->key() == Qt::Key_Escape && !menuBar()->isVisible()) { // this is if user did not noticed the hide menu key + hide_menuBar_action->toggle(); + return; } QMainWindow::keyPressEvent(event); diff --git a/src/window.h b/src/window.h index 6fcc3d9e..6ea34e94 100644 --- a/src/window.h +++ b/src/window.h @@ -5,6 +5,7 @@ #include #include #include +#include class Canvas; class ShaderLightPrefs; @@ -85,6 +86,11 @@ private slots: QMenu* const recent_files; QMenu* draw_menu; + QToolButton* shaderButton; + QToolBar* windowToolBar; + QMenu* projection_menu; + QToolButton* projectionButton; + QActionGroup* const recent_files_group; QAction* const recent_files_clear_action; const static int MAX_RECENT_FILES=8; From 639bf4a40843c277908673ce2e4f3d4f482d38ff Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 4 Feb 2023 22:21:21 +0100 Subject: [PATCH 20/35] Add set viewport size menu. Usefull to save given size screenshots. --- qt/icons/resolution_1_32.png | Bin 0 -> 757 bytes qt/qt.qrc | 1 + src/window.cpp | 71 ++++++++++++++++++++++++++++++++++- src/window.h | 2 + 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 qt/icons/resolution_1_32.png diff --git a/qt/icons/resolution_1_32.png b/qt/icons/resolution_1_32.png new file mode 100644 index 0000000000000000000000000000000000000000..7d655cc936512d76265b6fad9dfa874540eed91e GIT binary patch literal 757 zcmVk)uTq>dzLbV7gB*RK-C0Om% ziWZ`f!EB^9QAE%}qCZBWnI;`=oO#Z?pZiV=XUZ9&dBcnqG#^}e@7#CJ`|dsGe&+&I zP{IEW0swI0%3NJErrt$>4ntaAKqPjY8KoPz8G~;Lh>X#70I&c+WbKxP24{4>+9QT% zN4q{{?Hm9g8dL9VXlA!iSV%D1&p9yx3v$=fhMZ6sUEW>eJSQ!5Wq!n#rH*>l&CJJO0fU0R#{&O$~w9@1ijwxTjR9b31r= z_WH4~o!a0TlfJs17Ei}dF7CAOs42s>Rr_hC@ScRe#&7=#icons/reset_rotation_on_load.png icons/invert_zoom.png icons/auto_refresh.png + icons/resolution_1_32.png diff --git a/src/window.cpp b/src/window.cpp index 40c1a64c..92769971 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -43,6 +43,7 @@ Window::Window(QWidget *parent) : hide_menuBar_action(new QAction("Hide Menu Bar", this)), fullscreen_action(new QAction("Toggle Fullscreen",this)), resetTransformOnLoadAction(new QAction("Reset rotation on load",this)), + setGLSizeAction(new QAction("Set Viewport Size",this)), recent_files(new QMenu("Open recent", this)), recent_files_group(new QActionGroup(this)), recent_files_clear_action(new QAction("Clear recent files", this)), @@ -199,6 +200,53 @@ Window::Window(QWidget *parent) : this, &Window::on_fullscreen); this->addAction(fullscreen_action); + QMenu *resolutionMenu = view_menu->addMenu("Set Viewport Size"); + resolutionMenu->setIcon(QIcon(":/qt/icons/resolution_1_32.png")); + resolutionMenu->menuAction()->setIconVisibleInMenu(true); + QActionGroup* groupResolution = new QActionGroup(resolutionMenu); + + QAction *quatreTiers = new QAction("-- 4:3",this); + quatreTiers->setDisabled(true); + resolutionMenu->addAction(quatreTiers); + QAction *setResolution0Action = new QAction("640 x 480 (VGA)",this); + resolutionMenu->addAction(setResolution0Action); + groupResolution->addAction(setResolution0Action); + + QAction *setResolution1Action = new QAction("768 x 576 (PAL)",this); + resolutionMenu->addAction(setResolution1Action); + groupResolution->addAction(setResolution1Action); + + QAction *setResolution2Action = new QAction("800 x 600 (SVGA)",this); + resolutionMenu->addAction(setResolution2Action); + groupResolution->addAction(setResolution2Action); + + QAction *setResolution3Action = new QAction("1024 x 768 (XGA)",this); + resolutionMenu->addAction(setResolution3Action); + groupResolution->addAction(setResolution3Action); + + QAction *seizeNeuf = new QAction("-- 16:9",this); + seizeNeuf->setDisabled(true); + resolutionMenu->addAction(seizeNeuf); + + QAction *setResolution4Action = new QAction("800 x 480 (WVGA)",this); + resolutionMenu->addAction(setResolution4Action); + groupResolution->addAction(setResolution4Action); + + QAction *setResolution5Action = new QAction("1024 x 576 (16:9 PAL)",this); + resolutionMenu->addAction(setResolution5Action); + groupResolution->addAction(setResolution5Action); + + QAction *setResolution6Action = new QAction("1280 x 720 (HD720)",this); + resolutionMenu->addAction(setResolution6Action); + groupResolution->addAction(setResolution6Action); + + resolutionMenu->addSeparator(); + +// QAction *setCustomResolutionAction = new QAction("Custom Resolution",this); +// //connect(setCustomResolutionAction,SIGNAL(triggered(bool)),this,SLOT(setCustomResolution())); +// resolutionMenu->addAction(setCustomResolutionAction); + + connect(groupResolution,SIGNAL(triggered(QAction*)),this,SLOT(setViewportSize(QAction*))); auto help_menu = menuBar()->addMenu("Help"); help_menu->addAction(about_action); @@ -234,6 +282,15 @@ Window::Window(QWidget *parent) : windowToolBar->addSeparator(); // Third group + + QToolButton* viewportSizeButton = new QToolButton; + viewportSizeButton->setPopupMode(QToolButton::InstantPopup); + viewportSizeButton->setMenu(resolutionMenu); + viewportSizeButton->setIcon(resolutionMenu->icon()); + viewportSizeButton->setToolTip(resolutionMenu->title()); + viewportSizeButton->setFocusPolicy(Qt::NoFocus); // we do not want the button to have keyboard focus + windowToolBar->addWidget(viewportSizeButton); + windowToolBar->addAction(save_screenshot_action); windowToolBar->addAction(fullscreen_action); @@ -800,8 +857,20 @@ void Window::cycleShader(bool up) { // Resize the widget giving the canvas dimension // Useful for screenshot of given size. void Window::setCanvasSize(int w, int h) { - this->showNormal(); + if (this->isFullScreen()) { + fullscreen_action->toggle(); + } int dw = this->size().width() - canvas->size().width(); int dh = this->size().height() - canvas->size().height(); this->resize(w + dw, h + dh); } + +void Window::setViewportSize(QAction* act) { + QString t = act->text(); + QRegExp rx = QRegExp("^\\s*(\\d+).+(\\d+).*"); + rx.indexIn(t); + QStringList desc = rx.capturedTexts(); + int w = desc.at(1).toInt(); + int h = desc.at(2).toInt(); + setCanvasSize(w, h); +} diff --git a/src/window.h b/src/window.h index 6ea34e94..814c7801 100644 --- a/src/window.h +++ b/src/window.h @@ -57,6 +57,7 @@ private slots: void on_fullscreen(); void on_hide_menuBar(); void on_drawModePrefs(); + void setViewportSize(QAction* act); private: void rebuild_recent_files(); @@ -83,6 +84,7 @@ private slots: QAction* const hide_menuBar_action; QAction* const fullscreen_action; QAction* const resetTransformOnLoadAction; + QAction* const setGLSizeAction; QMenu* const recent_files; QMenu* draw_menu; From 1cd7051c2b1e4456be70e3251f150b8efbf24ce7 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Mon, 6 Feb 2023 16:34:39 +0100 Subject: [PATCH 21/35] Correct problem on fullscreen_action state when restoring fullscreen geometry. --- src/window.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/window.cpp b/src/window.cpp index 92769971..0929dd90 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -351,7 +351,12 @@ void Window::load_persist_settings(){ resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); -} + if (this->isFullScreen()) { + fullscreen_action->blockSignals(true); + fullscreen_action->setChecked(true); + fullscreen_action->blockSignals(false); + } + } void Window::on_drawModePrefs() { // For now only one draw mode has settings From 4e4f90844b323d2597bf715de6363ba8124a8b0b Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 00:09:21 +0100 Subject: [PATCH 22/35] Add geometry shader calc_altitudes.glsl to compute distance from the edges and allow drawing wireframe in the meshlight shader. Minor modifications to the other shaders and use version 330 --- gl/calc_altitudes.glsl | 64 +++++++++++++++++++++++++++++++++++++++ gl/gl.qrc | 3 +- gl/mesh.frag | 4 +-- gl/mesh.vert | 4 +-- gl/mesh_light.frag | 19 +++++++++--- gl/mesh_surfaceangle.frag | 4 +-- src/canvas.cpp | 5 +++ 7 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 gl/calc_altitudes.glsl diff --git a/gl/calc_altitudes.glsl b/gl/calc_altitudes.glsl new file mode 100644 index 00000000..721bed1f --- /dev/null +++ b/gl/calc_altitudes.glsl @@ -0,0 +1,64 @@ +#version 330 + +layout (triangles) in; +layout (triangle_strip, max_vertices = 3) out; + +out vec3 ec_pos; +noperspective out vec3 altitude; + +uniform vec2 portSize; + +void main() { + vec4 p0 = gl_in[0].gl_Position; + vec4 p1 = gl_in[1].gl_Position; + vec4 p2 = gl_in[2].gl_Position; + + vec2 p0_f = p0.xy/p0.w; + vec2 p1_f = p1.xy/p1.w; + vec2 p2_f = p2.xy/p2.w; + + // Altitude calculation : + // vp0p1 is the p0p1 vector + // vp0p2 is the p0p2 vector + // det(vp0p1,vp0p2) is the area of the parallelogram defined by the two vectors vp0p1 and vp0p2 + // h0 is the altitude from p0 in the triangle (p0 p1 p2) + // h0 multiplied by p1p2 which is the length of vp0p1-vp0p2 is also the area of the parallelogram defined by the two vectors vp0p1 and vp0p2 + // this leads to h0 + // + // portSize is used to have an altitude in pixel + // + + // Calculate h0 altitude from p0 + vec2 vp0p1 = portSize*(p1_f-p0_f); + vec2 vp0p2 = portSize*(p2_f-p0_f); + float h0 = abs(determinant(mat2(vp0p1,vp0p2))) / length(vp0p1-vp0p2); + // release values + gl_Position = p0; + ec_pos = gl_Position.xyz; + altitude = vec3(h0*p0.w, 0.0, 0.0); + EmitVertex(); + + // calculate h1 altitude from p1 + vec2 vp1p0 = portSize*(p0_f-p1_f); + vec2 vp1p2 = portSize*(p2_f-p1_f); + float h1 = abs(determinant(mat2(vp1p0,vp1p2))) / length(vp1p0-vp1p2); + // release values + gl_Position = p1; + ec_pos = gl_Position.xyz; + altitude = vec3(0.0, h1*p1.w, 0.0); + EmitVertex(); + + // calculate h2 altitude from p2 + vec2 vp2p0 = portSize*(p0_f-p2_f); + vec2 vp2p1 = portSize*(p1_f-p2_f); + float h2 = abs(determinant(mat2(vp2p0,vp2p1))) / length(vp2p0-vp2p1); + // release values + gl_Position = p2; + ec_pos = gl_Position.xyz; + altitude = vec3(0.0, 0.0, h2*p2.w); + EmitVertex(); + + EndPrimitive(); + +} + diff --git a/gl/gl.qrc b/gl/gl.qrc index a8065307..b66853df 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -1,5 +1,5 @@ - + mesh.frag mesh.vert mesh_wireframe.frag @@ -10,5 +10,6 @@ colored_lines.frag colored_lines.vert sphere.stl + calc_altitudes.glsl diff --git a/gl/mesh.frag b/gl/mesh.frag index d7a54d55..f9b67c7a 100644 --- a/gl/mesh.frag +++ b/gl/mesh.frag @@ -1,8 +1,8 @@ -#version 120 +#version 330 uniform float zoom; -varying vec3 ec_pos; +in vec3 ec_pos; void main() { vec3 base3 = vec3(0.99, 0.96, 0.89); diff --git a/gl/mesh.vert b/gl/mesh.vert index e60e76bb..875c3145 100644 --- a/gl/mesh.vert +++ b/gl/mesh.vert @@ -1,10 +1,10 @@ -#version 120 +#version 330 attribute vec3 vertex_position; uniform mat4 transform_matrix; uniform mat4 view_matrix; -varying vec3 ec_pos; +out vec3 ec_pos; void main() { gl_Position = view_matrix*transform_matrix* diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag index 31d43bb7..aae52db5 100644 --- a/gl/mesh_light.frag +++ b/gl/mesh_light.frag @@ -1,17 +1,20 @@ -#version 120 +#version 330 uniform float zoom; uniform vec4 ambient_light_color; uniform vec4 directive_light_color; uniform vec3 directive_light_direction; +uniform bool useWire; +uniform vec3 wireColor; +uniform float wireWidth; -varying vec3 ec_pos; +in vec3 ec_pos; +noperspective in vec3 altitude; void main() { // Normalize light direction vec3 dir = normalize(directive_light_direction); - // vec3 a = vec3(0.0, 1.0, 1.0); // normal vector vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); ec_normal.z *= zoom; @@ -20,7 +23,13 @@ void main() { vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; - // float coef = dot(ec_normal,dir); - // vec3 color = coef * lightcolor + (1.0 - coef) * objectcolor; + if (useWire) { + float d = min(min(altitude.x, altitude.y),altitude.z); + + float coef = exp2(-0.01*d*d); + float mixVal = smoothstep(wireWidth-1.0, wireWidth+1.0,d); + color = mix(wireColor,color,mixVal); + } + gl_FragColor = vec4(color, 1.0); } diff --git a/gl/mesh_surfaceangle.frag b/gl/mesh_surfaceangle.frag index 3ee94ddf..160df222 100644 --- a/gl/mesh_surfaceangle.frag +++ b/gl/mesh_surfaceangle.frag @@ -1,8 +1,8 @@ -#version 120 +#version 330 uniform float zoom; -varying vec3 ec_pos; +in vec3 ec_pos; void main() { vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); diff --git a/src/canvas.cpp b/src/canvas.cpp index 9faaf252..7e1f36d3 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -188,6 +188,7 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); mesh_meshlight_shader.addShader(mesh_vertshader); + qDebug() << "load frame" << mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl"); mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); mesh_meshlight_shader.link(); @@ -282,6 +283,10 @@ void Canvas::draw_mesh() // -1,-1,0 Light from top right //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); + glUniform1i(selected_mesh_shader->uniformLocation("useWire"),true); + glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),1.0); + glUniform2f(selected_mesh_shader->uniformLocation("portSize"),(float)this->width(),(float)this->height()); + glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),1.0,0.5,0.0); } // Find and enable the attribute location for vertex position From dccb65b1cd5fa16bd64ae3e615cf1a5b1b367750 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 10:56:59 +0100 Subject: [PATCH 23/35] Add parameters, Qsettings keys, default values for optional wire in meshlight --- src/canvas.cpp | 15 ++++++++++++--- src/canvas.h | 10 ++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 7e1f36d3..6074b6e8 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -16,12 +16,18 @@ const QString Canvas::AMBIENT_FACTOR = "ambientFactor"; const QString Canvas::DIRECTIVE_COLOR = "directiveColor"; const QString Canvas::DIRECTIVE_FACTOR = "directiveFactor"; const QString Canvas::CURRENT_LIGHT_DIRECTION = "currentLightDirection"; +const QString Canvas::USE_WIRE = "useWire"; +const QString Canvas::WIRE_WIDTH = "wireWidth"; +const QString Canvas::WIRE_COLOR = "wireColor"; const QColor Canvas::defaultAmbientColor = QColor::fromRgbF(0.22,0.8,1.0); const QColor Canvas::defaultDirectiveColor = QColor(255,255,255); const float Canvas::defaultAmbientFactor = 0.67; const float Canvas::defaultDirectiveFactor = 0.5; const int Canvas::defaultCurrentLightDirection = 1; +const bool Canvas::defaultUseWire = false; +const float Canvas::defaultWireWidth = 1.0; +const QColor Canvas::defaultWireColor = QColor(255,128,0); Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) : QOpenGLWidget(parent), mesh(nullptr), @@ -41,6 +47,9 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) directiveColor = settings.value(DIRECTIVE_COLOR,defaultDirectiveColor).value(); ambientFactor = settings.value(AMBIENT_FACTOR,defaultAmbientFactor).value(); directiveFactor = settings.value(DIRECTIVE_FACTOR,defaultDirectiveFactor).value(); + useWire = settings.value(USE_WIRE,defaultUseWire).value(); + wireWidth = settings.value(WIRE_WIDTH,defaultWireWidth).value(); + wireColor = settings.value(WIRE_COLOR,defaultWireColor).value(); // Fill direction list // Fill in directions @@ -283,10 +292,10 @@ void Canvas::draw_mesh() // -1,-1,0 Light from top right //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); - glUniform1i(selected_mesh_shader->uniformLocation("useWire"),true); - glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),1.0); + glUniform1i(selected_mesh_shader->uniformLocation("useWire"),useWire); + glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),wireWidth); glUniform2f(selected_mesh_shader->uniformLocation("portSize"),(float)this->width(),(float)this->height()); - glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),1.0,0.5,0.0); + glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),wireColor.redF(),wireColor.greenF(),wireColor.blueF()); } // Find and enable the attribute location for vertex position diff --git a/src/canvas.h b/src/canvas.h index 579710c5..b98bbbed 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -88,17 +88,27 @@ public slots: QList nameDir; QList listDir; int currentLightDirection; + bool useWire; + float wireWidth; + QColor wireColor; const static QColor defaultAmbientColor; const static QColor defaultDirectiveColor; const static float defaultAmbientFactor; const static float defaultDirectiveFactor; const static int defaultCurrentLightDirection; + const static bool defaultUseWire; + const static float defaultWireWidth; + const static QColor defaultWireColor; + const static QString AMBIENT_COLOR; const static QString AMBIENT_FACTOR; const static QString DIRECTIVE_COLOR; const static QString DIRECTIVE_FACTOR; const static QString CURRENT_LIGHT_DIRECTION; + const static QString USE_WIRE; + const static QString WIRE_WIDTH; + const static QString WIRE_COLOR; GLMesh* mesh; From eaae8c4b79952f9bf86377ec97a79c644226c6ba Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 11:38:50 +0100 Subject: [PATCH 24/35] Add getters and setters for new parameters --- src/canvas.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++--- src/canvas.h | 18 +++++++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 6074b6e8..d343d32e 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -22,11 +22,11 @@ const QString Canvas::WIRE_COLOR = "wireColor"; const QColor Canvas::defaultAmbientColor = QColor::fromRgbF(0.22,0.8,1.0); const QColor Canvas::defaultDirectiveColor = QColor(255,255,255); -const float Canvas::defaultAmbientFactor = 0.67; -const float Canvas::defaultDirectiveFactor = 0.5; +const double Canvas::defaultAmbientFactor = 0.67; +const double Canvas::defaultDirectiveFactor = 0.5; const int Canvas::defaultCurrentLightDirection = 1; const bool Canvas::defaultUseWire = false; -const float Canvas::defaultWireWidth = 1.0; +const double Canvas::defaultWireWidth = 1.0; const QColor Canvas::defaultWireColor = QColor(255,128,0); Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) @@ -549,3 +549,45 @@ void Canvas::setCurrentLightDirection(int ind) { void Canvas::resetCurrentLightDirection() { setCurrentLightDirection(defaultCurrentLightDirection); } + +bool Canvas::getUseWire() { + return useWire; +} + +void Canvas::setUseWire(bool b) { + useWire = b; + QSettings settings; + settings.setValue(USE_WIRE,useWire); +} + +void Canvas::resetUseWire() { + setUseWire(defaultUseWire); +} + +double Canvas::getWireWidth() { + return (double) wireWidth; +} + +void Canvas::setWireWidth(double w) { + wireWidth = (float) w; + QSettings settings; + settings.setValue(WIRE_WIDTH,w); +} + +void Canvas::resetWireWidth() { + setWireWidth(defaultWireWidth); +} + +QColor Canvas::getWireColor() { + return wireColor; +} + +void Canvas::setWireColor(QColor c) { + wireColor = c; + QSettings settings; + settings.setValue(WIRE_COLOR,wireColor); +} + +void Canvas::resetWireColor() { + setWireColor(defaultWireColor); +} diff --git a/src/canvas.h b/src/canvas.h index b98bbbed..9f2d7316 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -46,6 +46,18 @@ class Canvas : public QOpenGLWidget, protected QOpenGLFunctions void setCurrentLightDirection(int ind); void resetCurrentLightDirection(); + bool getUseWire(); + void setUseWire(bool b); + void resetUseWire(); + + double getWireWidth(); + void setWireWidth(double w); + void resetWireWidth(); + + QColor getWireColor(); + void setWireColor(QColor c); + void resetWireColor(); + public slots: void set_status(const QString& s); void clear_status(); @@ -94,11 +106,11 @@ public slots: const static QColor defaultAmbientColor; const static QColor defaultDirectiveColor; - const static float defaultAmbientFactor; - const static float defaultDirectiveFactor; + const static double defaultAmbientFactor; + const static double defaultDirectiveFactor; const static int defaultCurrentLightDirection; const static bool defaultUseWire; - const static float defaultWireWidth; + const static double defaultWireWidth; const static QColor defaultWireColor; const static QString AMBIENT_COLOR; From b3e8445d6adf7e9375878e30b63ea3d014b1a24e Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 14:38:00 +0100 Subject: [PATCH 25/35] Remove unused value in mesh_light.frag --- gl/mesh_light.frag | 2 -- 1 file changed, 2 deletions(-) diff --git a/gl/mesh_light.frag b/gl/mesh_light.frag index aae52db5..d2ceaa2b 100644 --- a/gl/mesh_light.frag +++ b/gl/mesh_light.frag @@ -25,8 +25,6 @@ void main() { if (useWire) { float d = min(min(altitude.x, altitude.y),altitude.z); - - float coef = exp2(-0.01*d*d); float mixVal = smoothstep(wireWidth-1.0, wireWidth+1.0,d); color = mix(wireColor,color,mixVal); } From 271e35f2b5fadb94e6013b201d582097931396cf Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 14:38:46 +0100 Subject: [PATCH 26/35] Add wireframe parameters to shaderlightprefs --- src/shaderlightprefs.cpp | 72 ++++++++++++++++++++++++++++++++++++++++ src/shaderlightprefs.h | 13 ++++++++ 2 files changed, 85 insertions(+) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index d00c7074..6a9f8e6c 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -70,6 +70,7 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p // Fill in directions comboDirections = new QComboBox; + comboDirections->setFocusPolicy(Qt::NoFocus); middleLayout->addWidget(comboDirections,2,1,1,2); comboDirections->addItems(canvas->getNameDir()); comboDirections->setCurrentIndex(canvas->getCurrentLightDirection()); @@ -80,6 +81,41 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p buttonResetDirection->setFocusPolicy(Qt::NoFocus); connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); + checkboxUseWireFrame = new QCheckBox("Add wireframe"); + checkboxUseWireFrame->setChecked(canvas->getUseWire()); + middleLayout->addWidget(checkboxUseWireFrame,3,0); + checkboxUseWireFrame->setFocusPolicy(Qt::NoFocus); + connect(checkboxUseWireFrame,SIGNAL(stateChanged(int)),this,SLOT(checkboxUseWireFrameChanged())); + + QLabel* labelWireColor = new QLabel("Wire Color"); + middleLayout->addWidget(labelWireColor,4,0); + dummy.fill(canvas->getWireColor()); + buttonWireColor = new QPushButton; + buttonWireColor->setIcon(QIcon(dummy)); + middleLayout->addWidget(buttonWireColor,4,1); + buttonWireColor->setFocusPolicy(Qt::NoFocus); + QPushButton* buttonResetWireColor = new QPushButton("Reset"); + buttonResetWireColor->setFocusPolicy(Qt::NoFocus); + middleLayout->addWidget(buttonResetWireColor,4,3); + connect(buttonWireColor,SIGNAL(clicked(bool)),this,SLOT(buttonWireColorClicked())); + connect(buttonResetWireColor,SIGNAL(clicked(bool)),this,SLOT(resetWireColorClicked())); + + labelWireWidth = new QLabel(QString("Wire Width : %1").arg((int)canvas->getWireWidth())); + middleLayout->addWidget(labelWireWidth,5,0); + sliderWireWidth = new QSlider(Qt::Horizontal); + sliderWireWidth->setFocusPolicy(Qt::NoFocus); + sliderWireWidth->setRange(1,10); + sliderWireWidth->setTickPosition(QSlider::TicksBelow); + sliderWireWidth->setSingleStep(1); + sliderWireWidth->setPageStep(1); + sliderWireWidth->setValue((int)canvas->getWireWidth()); + middleLayout->addWidget(sliderWireWidth,5,1,1,2); + connect(sliderWireWidth,SIGNAL(valueChanged(int)),this,SLOT(sliderWireWidthChanged())); + QPushButton* buttonResetLineWidth = new QPushButton("Reset"); + buttonResetLineWidth->setFocusPolicy(Qt::NoFocus); + middleLayout->addWidget(buttonResetLineWidth,5,3); + connect(buttonResetLineWidth,SIGNAL(clicked(bool)),this,SLOT(resetWireWidthClicked())); + // Ok button QWidget* boxButton = new QWidget; @@ -177,4 +213,40 @@ void ShaderLightPrefs::moveEvent(QMoveEvent *event) QSettings().setValue(PREFS_GEOM, saveGeometry()); } +void ShaderLightPrefs::checkboxUseWireFrameChanged() { + bool state = checkboxUseWireFrame->isChecked(); + canvas->setUseWire(state); + canvas->update(); +} + +void ShaderLightPrefs::buttonWireColorClicked() { + QColor newColor = QColorDialog::getColor(canvas->getWireColor(), this, QString("Choose color"),QColorDialog::DontUseNativeDialog); + if (newColor.isValid() == true) + { + canvas->setWireColor(newColor); + QPixmap dummy(20, 20); + dummy.fill(canvas->getWireColor()); + buttonWireColor->setIcon(QIcon(dummy)); + canvas->update(); + } +} + +void ShaderLightPrefs::resetWireColorClicked() { + canvas->resetWireColor(); + QPixmap dummy(20, 20); + dummy.fill(canvas->getWireColor()); + buttonWireColor->setIcon(QIcon(dummy)); + canvas->update(); +} +void ShaderLightPrefs::sliderWireWidthChanged() { + int lw = sliderWireWidth->value(); + canvas->setWireWidth((double) lw); + labelWireWidth->setText(QString("Wire Width : %1").arg(lw)); + canvas->update(); +} + +void ShaderLightPrefs::resetWireWidthClicked() { + canvas->resetWireWidth(); + sliderWireWidth->setValue((int)canvas->getWireWidth()); +} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index 9f573399..abe83cde 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -7,6 +7,8 @@ class Canvas; class QLabel; class QLineEdit; class QComboBox; +class QCheckBox; +class QSlider; class ShaderLightPrefs : public QDialog { @@ -30,6 +32,12 @@ private slots: void comboDirectionsChanged(int ind); void resetDirection(); + void checkboxUseWireFrameChanged(); + void buttonWireColorClicked(); + void resetWireColorClicked(); + void sliderWireWidthChanged(); + void resetWireWidthClicked(); + void okButtonClicked(); private: @@ -40,6 +48,11 @@ private slots: QLineEdit* editDirectiveFactor; QComboBox* comboDirections; + QCheckBox* checkboxUseWireFrame; + QPushButton* buttonWireColor; + QLabel* labelWireWidth; + QSlider* sliderWireWidth; + const static QString PREFS_GEOM; }; From 19bd5215b81829451e54c3b8bd493f4830fd9b26 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 23:18:28 +0100 Subject: [PATCH 27/35] Add a fallback mode to glsl 120 if available glsl version is less than 330 in which case drawing wireframe on top of the meshlight shader is not available. --- gl/gl.qrc | 1 + gl/mesh.frag | 2 +- gl/mesh.vert | 4 ++-- gl/mesh_light_120.frag | 23 +++++++++++++++++++++++ gl/mesh_surfaceangle.frag | 2 +- src/canvas.cpp | 34 ++++++++++++++++++++++++++++------ src/canvas.h | 6 ++++++ src/shaderlightprefs.cpp | 26 +++++++++++++++++++------- src/shaderlightprefs.h | 3 +++ src/window.cpp | 1 + 10 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 gl/mesh_light_120.frag diff --git a/gl/gl.qrc b/gl/gl.qrc index b66853df..6d919e69 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -11,5 +11,6 @@ colored_lines.vert sphere.stl calc_altitudes.glsl + mesh_light_120.frag diff --git a/gl/mesh.frag b/gl/mesh.frag index f9b67c7a..b90a5d80 100644 --- a/gl/mesh.frag +++ b/gl/mesh.frag @@ -1,4 +1,4 @@ -#version 330 +#version 120 uniform float zoom; diff --git a/gl/mesh.vert b/gl/mesh.vert index 875c3145..e60e76bb 100644 --- a/gl/mesh.vert +++ b/gl/mesh.vert @@ -1,10 +1,10 @@ -#version 330 +#version 120 attribute vec3 vertex_position; uniform mat4 transform_matrix; uniform mat4 view_matrix; -out vec3 ec_pos; +varying vec3 ec_pos; void main() { gl_Position = view_matrix*transform_matrix* diff --git a/gl/mesh_light_120.frag b/gl/mesh_light_120.frag new file mode 100644 index 00000000..89242151 --- /dev/null +++ b/gl/mesh_light_120.frag @@ -0,0 +1,23 @@ +#version 120 + +uniform float zoom; +uniform vec4 ambient_light_color; +uniform vec4 directive_light_color; +uniform vec3 directive_light_direction; + +in vec3 ec_pos; + +void main() { + // Normalize light direction + vec3 dir = normalize(directive_light_direction); + + // normal vector + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + + vec3 color = ambient_light_color.w * ambient_light_color.xyz + directive_light_color.w * dot(ec_normal,dir) * directive_light_color.xyz; + + gl_FragColor = vec4(color, 1.0); +} diff --git a/gl/mesh_surfaceangle.frag b/gl/mesh_surfaceangle.frag index 160df222..f4dac15d 100644 --- a/gl/mesh_surfaceangle.frag +++ b/gl/mesh_surfaceangle.frag @@ -1,4 +1,4 @@ -#version 330 +#version 120 uniform float zoom; diff --git a/src/canvas.cpp b/src/canvas.cpp index d343d32e..0838b712 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -42,6 +42,7 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) currentTransform = QMatrix4x4(); resetTransform(); + fallbackGlsl = false; QSettings settings; ambientColor = settings.value(AMBIENT_COLOR,defaultAmbientColor).value(); directiveColor = settings.value(DIRECTIVE_COLOR,defaultDirectiveColor).value(); @@ -76,6 +77,7 @@ Canvas::Canvas(const QSurfaceFormat& format, QWidget *parent) } anim.setDuration(100); + } Canvas::~Canvas() @@ -185,6 +187,12 @@ void Canvas::initializeGL() { initializeOpenGLFunctions(); + + double glslVersion = QString((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)).toDouble(); + //double glslVersion = QString("3.30").toDouble(); + fallbackGlsl = glslVersion <= 3.29 ? true : false; + emit fallbackGlslUpdated(fallbackGlsl); + mesh_vertshader = new QOpenGLShader(QOpenGLShader::Vertex); mesh_vertshader->compileSourceFile(":/gl/mesh.vert"); mesh_shader.addShader(mesh_vertshader); @@ -197,8 +205,16 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); mesh_meshlight_shader.addShader(mesh_vertshader); - qDebug() << "load frame" << mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl"); - mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + // If glsl 330 is available + if (!fallbackGlsl) { + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl"); + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + } else { + // fallback to 120 + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light_120.frag"); + qDebug() << "Cannot load a shader using glsl version 330, fall back to another using version 120"; + qDebug() << "Adding wireframe on top of meshlight shader will be disabled."; + } mesh_meshlight_shader.link(); backdrop = new Backdrop(); @@ -292,10 +308,12 @@ void Canvas::draw_mesh() // -1,-1,0 Light from top right //glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),-1.0f,-1.0f,0.0f); glUniform3f(selected_mesh_shader->uniformLocation("directive_light_direction"),listDir.at(currentLightDirection).x(), listDir.at(currentLightDirection).y(), listDir.at(currentLightDirection).z()); - glUniform1i(selected_mesh_shader->uniformLocation("useWire"),useWire); - glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),wireWidth); - glUniform2f(selected_mesh_shader->uniformLocation("portSize"),(float)this->width(),(float)this->height()); - glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),wireColor.redF(),wireColor.greenF(),wireColor.blueF()); + if (!fallbackGlsl) { + glUniform1i(selected_mesh_shader->uniformLocation("useWire"),useWire); + glUniform1f(selected_mesh_shader->uniformLocation("wireWidth"),wireWidth); + glUniform2f(selected_mesh_shader->uniformLocation("portSize"),(float)this->width(),(float)this->height()); + glUniform3f(selected_mesh_shader->uniformLocation("wireColor"),wireColor.redF(),wireColor.greenF(),wireColor.blueF()); + } } // Find and enable the attribute location for vertex position @@ -591,3 +609,7 @@ void Canvas::setWireColor(QColor c) { void Canvas::resetWireColor() { setWireColor(defaultWireColor); } + +bool Canvas::isFallbackGlsl() { + return fallbackGlsl; +} diff --git a/src/canvas.h b/src/canvas.h index 9f2d7316..a5994363 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -58,6 +58,8 @@ class Canvas : public QOpenGLWidget, protected QOpenGLFunctions void setWireColor(QColor c); void resetWireColor(); + bool isFallbackGlsl(); + public slots: void set_status(const QString& s); void clear_status(); @@ -76,6 +78,9 @@ public slots: void set_perspective(float p); void view_anim(float v); +signals: + void fallbackGlslUpdated(bool b); + private: void draw_mesh(); @@ -103,6 +108,7 @@ public slots: bool useWire; float wireWidth; QColor wireColor; + bool fallbackGlsl; const static QColor defaultAmbientColor; const static QColor defaultDirectiveColor; diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 6a9f8e6c..bd60cd36 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -81,27 +81,32 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p buttonResetDirection->setFocusPolicy(Qt::NoFocus); connect(buttonResetDirection,SIGNAL(clicked(bool)),this,SLOT(resetDirection())); + + groupWireFrame = new QFrame; + QGridLayout* groupWireFrameLayout = new QGridLayout; + groupWireFrame->setLayout(groupWireFrameLayout); + checkboxUseWireFrame = new QCheckBox("Add wireframe"); checkboxUseWireFrame->setChecked(canvas->getUseWire()); - middleLayout->addWidget(checkboxUseWireFrame,3,0); + groupWireFrameLayout->addWidget(checkboxUseWireFrame,0,0); checkboxUseWireFrame->setFocusPolicy(Qt::NoFocus); connect(checkboxUseWireFrame,SIGNAL(stateChanged(int)),this,SLOT(checkboxUseWireFrameChanged())); QLabel* labelWireColor = new QLabel("Wire Color"); - middleLayout->addWidget(labelWireColor,4,0); + groupWireFrameLayout->addWidget(labelWireColor,1,0); dummy.fill(canvas->getWireColor()); buttonWireColor = new QPushButton; buttonWireColor->setIcon(QIcon(dummy)); - middleLayout->addWidget(buttonWireColor,4,1); + groupWireFrameLayout->addWidget(buttonWireColor,1,1); buttonWireColor->setFocusPolicy(Qt::NoFocus); QPushButton* buttonResetWireColor = new QPushButton("Reset"); buttonResetWireColor->setFocusPolicy(Qt::NoFocus); - middleLayout->addWidget(buttonResetWireColor,4,3); + groupWireFrameLayout->addWidget(buttonResetWireColor,1,3); connect(buttonWireColor,SIGNAL(clicked(bool)),this,SLOT(buttonWireColorClicked())); connect(buttonResetWireColor,SIGNAL(clicked(bool)),this,SLOT(resetWireColorClicked())); labelWireWidth = new QLabel(QString("Wire Width : %1").arg((int)canvas->getWireWidth())); - middleLayout->addWidget(labelWireWidth,5,0); + groupWireFrameLayout->addWidget(labelWireWidth,2,0); sliderWireWidth = new QSlider(Qt::Horizontal); sliderWireWidth->setFocusPolicy(Qt::NoFocus); sliderWireWidth->setRange(1,10); @@ -109,13 +114,14 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p sliderWireWidth->setSingleStep(1); sliderWireWidth->setPageStep(1); sliderWireWidth->setValue((int)canvas->getWireWidth()); - middleLayout->addWidget(sliderWireWidth,5,1,1,2); + groupWireFrameLayout->addWidget(sliderWireWidth,2,1,1,2); connect(sliderWireWidth,SIGNAL(valueChanged(int)),this,SLOT(sliderWireWidthChanged())); QPushButton* buttonResetLineWidth = new QPushButton("Reset"); buttonResetLineWidth->setFocusPolicy(Qt::NoFocus); - middleLayout->addWidget(buttonResetLineWidth,5,3); + groupWireFrameLayout->addWidget(buttonResetLineWidth,2,3); connect(buttonResetLineWidth,SIGNAL(clicked(bool)),this,SLOT(resetWireWidthClicked())); + middleLayout->addWidget(groupWireFrame,3,0,3,4); // Ok button QWidget* boxButton = new QWidget; @@ -134,6 +140,8 @@ ShaderLightPrefs::ShaderLightPrefs(QWidget *parent, Canvas *_canvas) : QDialog(p if (!settings.value(PREFS_GEOM).isNull()) { restoreGeometry(settings.value(PREFS_GEOM).toByteArray()); } + + connect(canvas,SIGNAL(fallbackGlslUpdated(bool)),this,SLOT(onFallbackGlslUpdated(bool))); } void ShaderLightPrefs::buttonAmbientColorClicked() { @@ -250,3 +258,7 @@ void ShaderLightPrefs::resetWireWidthClicked() { canvas->resetWireWidth(); sliderWireWidth->setValue((int)canvas->getWireWidth()); } + +void ShaderLightPrefs::onFallbackGlslUpdated(bool b) { + groupWireFrame->setDisabled(b); +} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index abe83cde..2128699f 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -9,6 +9,7 @@ class QLineEdit; class QComboBox; class QCheckBox; class QSlider; +class QFrame; class ShaderLightPrefs : public QDialog { @@ -39,6 +40,7 @@ private slots: void resetWireWidthClicked(); void okButtonClicked(); + void onFallbackGlslUpdated(bool b); private: Canvas* canvas; @@ -48,6 +50,7 @@ private slots: QLineEdit* editDirectiveFactor; QComboBox* comboDirections; + QFrame* groupWireFrame; QCheckBox* checkboxUseWireFrame; QPushButton* buttonWireColor; QLabel* labelWireWidth; diff --git a/src/window.cpp b/src/window.cpp index 0929dd90..19eea221 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -64,6 +64,7 @@ Window::Window(QWidget *parent) : canvas = new Canvas(format, this); setCentralWidget(canvas); + canvas->update(); meshlightprefs = new ShaderLightPrefs(this, canvas); From 5eaba1f4b42ac3a40ce355d06a3ba6dcc0ebeccc Mon Sep 17 00:00:00 2001 From: William Daniau Date: Sat, 11 Feb 2023 23:29:59 +0100 Subject: [PATCH 28/35] change back in to varying for glsl 120 shaders --- gl/mesh.frag | 2 +- gl/mesh_light_120.frag | 2 +- gl/mesh_surfaceangle.frag | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gl/mesh.frag b/gl/mesh.frag index b90a5d80..d7a54d55 100644 --- a/gl/mesh.frag +++ b/gl/mesh.frag @@ -2,7 +2,7 @@ uniform float zoom; -in vec3 ec_pos; +varying vec3 ec_pos; void main() { vec3 base3 = vec3(0.99, 0.96, 0.89); diff --git a/gl/mesh_light_120.frag b/gl/mesh_light_120.frag index 89242151..4c671470 100644 --- a/gl/mesh_light_120.frag +++ b/gl/mesh_light_120.frag @@ -5,7 +5,7 @@ uniform vec4 ambient_light_color; uniform vec4 directive_light_color; uniform vec3 directive_light_direction; -in vec3 ec_pos; +varying vec3 ec_pos; void main() { // Normalize light direction diff --git a/gl/mesh_surfaceangle.frag b/gl/mesh_surfaceangle.frag index f4dac15d..3ee94ddf 100644 --- a/gl/mesh_surfaceangle.frag +++ b/gl/mesh_surfaceangle.frag @@ -2,7 +2,7 @@ uniform float zoom; -in vec3 ec_pos; +varying vec3 ec_pos; void main() { vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); From 9681916b137acf7c7a002027038da4d33f60c2b8 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Mon, 13 Feb 2023 10:28:42 +0100 Subject: [PATCH 29/35] Save hide menu state --- src/window.cpp | 10 ++++++++++ src/window.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/window.cpp b/src/window.cpp index 19eea221..dd89ba90 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -13,6 +13,7 @@ const QString Window::PROJECTION_KEY = "projection"; const QString Window::DRAW_MODE_KEY = "drawMode"; const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; const QString Window::RESET_TRANSFORM_ON_LOAD_KEY = "resetTransformOnLoad"; +const QString Window::HIDE_MENU_BAR = "hideMenuBar"; const QKeySequence Window::shortcutOpen = Qt::Key_O; const QKeySequence Window::shortcutReload = Qt::Key_R; @@ -350,6 +351,13 @@ void Window::load_persist_settings(){ dm_acts[draw_mode]->setChecked(true); on_drawMode(dm_acts[draw_mode]); + // menu bar + bool hideMenu = settings.value(HIDE_MENU_BAR, false).toBool(); + hide_menuBar_action->blockSignals(true); + hide_menuBar_action->setChecked(hideMenu); + on_hide_menuBar(); + hide_menuBar_action->blockSignals(false); + resize(600, 400); restoreGeometry(settings.value(WINDOW_GEOM_KEY).toByteArray()); if (this->isFullScreen()) { @@ -585,6 +593,8 @@ void Window::on_hide_menuBar() { menuBar()->setVisible(!hide_menuBar_action->isChecked()); windowToolBar->setVisible(!hide_menuBar_action->isChecked()); + QSettings settings; + settings.setValue(HIDE_MENU_BAR,hide_menuBar_action->isChecked()); } void Window::rebuild_recent_files() diff --git a/src/window.h b/src/window.h index 814c7801..31e5c130 100644 --- a/src/window.h +++ b/src/window.h @@ -104,6 +104,8 @@ private slots: const static QString DRAW_MODE_KEY; const static QString WINDOW_GEOM_KEY; const static QString RESET_TRANSFORM_ON_LOAD_KEY; + const static QString HIDE_MENU_BAR; + const static QKeySequence shortcutOpen; const static QKeySequence shortcutReload; From d31625568d65571a877b613a5856399ffe5280fe Mon Sep 17 00:00:00 2001 From: William Daniau Date: Tue, 14 Feb 2023 11:09:33 +0100 Subject: [PATCH 30/35] Use a regular expression instead of a simple toDouble to get glsl version as nividia for example will have something like 4.60 NVIDIA and the toDouble conversion will result in 0 --- src/canvas.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index 0838b712..ca935eee 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -187,9 +187,13 @@ void Canvas::initializeGL() { initializeOpenGLFunctions(); - - double glslVersion = QString((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)).toDouble(); - //double glslVersion = QString("3.30").toDouble(); + double glslVersion = 0.0; + QString glslVersionString = QString((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + QRegularExpression re("^.*(\\d+\\.\\d+).*$"); + QRegularExpressionMatch match = re.match(glslVersionString); + if (match.hasMatch()) { + glslVersion = match.captured(1).toDouble(); + } fallbackGlsl = glslVersion <= 3.29 ? true : false; emit fallbackGlslUpdated(fallbackGlsl); From 6d0a2e00cbfc249290b88a5021fb393cc4228a74 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Tue, 14 Feb 2023 13:14:50 +0100 Subject: [PATCH 31/35] Instead of tryng to guess glsl supported version, try to load the version 330 shaders and fallback if not successful --- src/canvas.cpp | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/canvas.cpp b/src/canvas.cpp index ca935eee..79cca02e 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -187,15 +187,7 @@ void Canvas::initializeGL() { initializeOpenGLFunctions(); - double glslVersion = 0.0; - QString glslVersionString = QString((char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); - QRegularExpression re("^.*(\\d+\\.\\d+).*$"); - QRegularExpressionMatch match = re.match(glslVersionString); - if (match.hasMatch()) { - glslVersion = match.captured(1).toDouble(); - } - fallbackGlsl = glslVersion <= 3.29 ? true : false; - emit fallbackGlslUpdated(fallbackGlsl); + fallbackGlsl = false; mesh_vertshader = new QOpenGLShader(QOpenGLShader::Vertex); mesh_vertshader->compileSourceFile(":/gl/mesh.vert"); @@ -209,16 +201,16 @@ void Canvas::initializeGL() mesh_surfaceangle_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_surfaceangle.frag"); mesh_surfaceangle_shader.link(); mesh_meshlight_shader.addShader(mesh_vertshader); - // If glsl 330 is available - if (!fallbackGlsl) { - mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl"); - mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); - } else { + bool loadSuccess330 = mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Geometry, ":/gl/calc_altitudes.glsl") && + mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); + if (!loadSuccess330) { // fallback to 120 + fallbackGlsl = true; mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light_120.frag"); qDebug() << "Cannot load a shader using glsl version 330, fall back to another using version 120"; qDebug() << "Adding wireframe on top of meshlight shader will be disabled."; } + emit fallbackGlslUpdated(fallbackGlsl); mesh_meshlight_shader.link(); backdrop = new Backdrop(); From 04ec17b9c11353bf42512860f0bf48fdc1e817bd Mon Sep 17 00:00:00 2001 From: William Daniau Date: Thu, 16 Feb 2023 10:33:48 +0100 Subject: [PATCH 32/35] Add W shortcut to toggle use of wireframe on top of meshlight shader --- src/shaderlightprefs.cpp | 4 ++++ src/shaderlightprefs.h | 1 + src/window.cpp | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index bd60cd36..1a5d6690 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -262,3 +262,7 @@ void ShaderLightPrefs::resetWireWidthClicked() { void ShaderLightPrefs::onFallbackGlslUpdated(bool b) { groupWireFrame->setDisabled(b); } + +void ShaderLightPrefs::toggleUseWire() { + checkboxUseWireFrame->toggle(); +} diff --git a/src/shaderlightprefs.h b/src/shaderlightprefs.h index 2128699f..0e6b79ee 100644 --- a/src/shaderlightprefs.h +++ b/src/shaderlightprefs.h @@ -16,6 +16,7 @@ class ShaderLightPrefs : public QDialog Q_OBJECT public: ShaderLightPrefs(QWidget* parent, Canvas* _canvas); + void toggleUseWire(); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/src/window.cpp b/src/window.cpp index dd89ba90..fb9f0518 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -835,6 +835,10 @@ void Window::keyPressEvent(QKeyEvent* event) } else if (event->key() == Qt::Key_Escape && !menuBar()->isVisible()) { // this is if user did not noticed the hide menu key hide_menuBar_action->toggle(); return; + } else if (event->key() == Qt::Key_W) { + if (dm_acts.at(getCurrentShader()) == meshlight_action) { + meshlightprefs->toggleUseWire(); + } } QMainWindow::keyPressEvent(event); From ac51204cf24292ab853de2ce565765c26807c409 Mon Sep 17 00:00:00 2001 From: William Daniau Date: Thu, 16 Feb 2023 13:49:46 +0100 Subject: [PATCH 33/35] Only toggle checkbox if enabled --- src/shaderlightprefs.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index 1a5d6690..ec7338a1 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -264,5 +264,7 @@ void ShaderLightPrefs::onFallbackGlslUpdated(bool b) { } void ShaderLightPrefs::toggleUseWire() { - checkboxUseWireFrame->toggle(); + // toggle if enable, no sense to do so otherwise + if (checkboxUseWireFrame->isEnabled()) + checkboxUseWireFrame->toggle(); } From a236a6c6219d36654955de90f00a998d9de7cbbf Mon Sep 17 00:00:00 2001 From: William Daniau Date: Fri, 24 Feb 2023 16:52:13 +0100 Subject: [PATCH 34/35] Process events to avoid warnings --- src/shaderlightprefs.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shaderlightprefs.cpp b/src/shaderlightprefs.cpp index ec7338a1..bb4cc38d 100644 --- a/src/shaderlightprefs.cpp +++ b/src/shaderlightprefs.cpp @@ -214,11 +214,13 @@ void ShaderLightPrefs::resetDirection() { void ShaderLightPrefs::resizeEvent(QResizeEvent *event) { QSettings().setValue(PREFS_GEOM, saveGeometry()); + QWidget::resizeEvent(event); } void ShaderLightPrefs::moveEvent(QMoveEvent *event) { QSettings().setValue(PREFS_GEOM, saveGeometry()); + QWidget::moveEvent(event); } void ShaderLightPrefs::checkboxUseWireFrameChanged() { From b627905e3ed03bcc71bc979ed3759849884a9a2b Mon Sep 17 00:00:00 2001 From: William Daniau Date: Fri, 24 Feb 2023 16:53:36 +0100 Subject: [PATCH 35/35] Reduce requirement on Qt version to 5.12 to be able to compile on ubuntu 20.04 and add a conditional compilation for obsolete method. --- CMakeLists.txt | 2 +- src/canvas.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f376855..6b67bd83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ set(Icon_Resource exe/fstl.rc) set(OpenGL_GL_PREFERENCE GLVND) #find required packages. -find_package(Qt5 5.14 REQUIRED COMPONENTS Core Gui Widgets OpenGL) +find_package(Qt5 5.12 REQUIRED COMPONENTS Core Gui Widgets OpenGL) find_package(OpenGL REQUIRED) find_package(Threads REQUIRED) diff --git a/src/canvas.cpp b/src/canvas.cpp index 79cca02e..0349660d 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -461,7 +461,13 @@ void Canvas::wheelEvent(QWheelEvent *event) { // Find GL position before the zoom operation // (to zoom about mouse cursor) +// event->pos() obsolete since introduction of event->position() in 5.14 +// but we still want to be able compile with 5.12 which is the minimum requirement in CmakeLists.txt +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + auto p = event->pos(); +#else auto p = event->position(); +#endif QVector3D v(1 - p.x() / (0.5*width()), p.y() / (0.5*height()) - 1, 0); QVector3D a = transform_matrix().inverted() *