diff --git a/data/assets/base.asset b/data/assets/base.asset index ee94a61231..d857183682 100644 --- a/data/assets/base.asset +++ b/data/assets/base.asset @@ -18,6 +18,7 @@ asset.require('scene/solarsystem/dwarf_planets/pluto/charon/default_layers') asset.require('scene/milkyway/milkyway/volume') asset.require('scene/milkyway/constellations/constellation_art') asset.require('scene/milkyway/constellations/constellation_keybinds') +asset.require('scene/milkyway/objects/orionnebula/orionnebula') asset.require('util/launcher_images') local assetHelper = asset.require('util/asset_helper') diff --git a/data/assets/scene/digitaluniverse/grids.asset b/data/assets/scene/digitaluniverse/grids.asset index 2699247df4..daf5d5fa20 100644 --- a/data/assets/scene/digitaluniverse/grids.asset +++ b/data/assets/scene/digitaluniverse/grids.asset @@ -550,6 +550,6 @@ asset.meta = { License = "AMNH Digital Universe", Identifiers = {"RadioSphere", "OortSphere", "EclipticSphere", "EclipticSphereLabels", "Equatorial", "EquatorialSphereLabels", "GalacticSphere", "GalacticSphereLabels", - "1ldGrid", "1lmGrid", "1lyGrid", "10lyGrid", "100lyGrid", "1klyGrid" "10klyGrid", + "1ldGrid", "1lmGrid", "1lyGrid", "10lyGrid", "100lyGrid", "1klyGrid", "10klyGrid", "100klyGrid", "1MlyGrid", "10MlyGrid", "100MlyGrid", "20GlyGrid"} } diff --git a/data/assets/scene/milkyway/objects/orionnebula/cluster.asset b/data/assets/scene/milkyway/objects/orionnebula/cluster.asset index 910aca0673..8a49c4bc33 100644 --- a/data/assets/scene/milkyway/objects/orionnebula/cluster.asset +++ b/data/assets/scene/milkyway/objects/orionnebula/cluster.asset @@ -16,7 +16,7 @@ local OrionClusterStars = { Type = "RenderableStars", File = sync .. "/oricluster.speck", Texture = sync .. "/halo.png", - Texture = sync .. "/colorbv.cmap", + ColorMap = sync .. "/colorbv.cmap", MagnitudeExponent = 5.02, SizeComposition = "Distance Modulus", RenderMethod = "Texture Based" diff --git a/data/assets/scene/milkyway/objects/orionnebula/nebula.asset b/data/assets/scene/milkyway/objects/orionnebula/nebula.asset index dc99a09a4f..46a2303a5f 100644 --- a/data/assets/scene/milkyway/objects/orionnebula/nebula.asset +++ b/data/assets/scene/milkyway/objects/orionnebula/nebula.asset @@ -54,8 +54,9 @@ local OrionNebulaModel = { Opacity = 1.0, DisableFaceCulling = false, SpecularIntensity = 0.0, - AmbientIntensity = 0.45, - DiffuseIntensity = 0.0, + AmbientIntensity = 0.0, + DiffuseIntensity = 1.0, + --PerformShading = false, RotationVector = { 0.000000, 22.300000, 0.000000 }, LightSources = LIGHTS; }, @@ -85,8 +86,9 @@ local OrionNebulaShocksModel = { Opacity = 1.0, DisableFaceCulling = false, SpecularIntensity = 0.0, - AmbientIntensity = 0.19, - DiffuseIntensity = 0.4, + AmbientIntensity = 0.0, + DiffuseIntensity = 1.0, + --PerformShading = false, RotationVector = { 0.000000, 22.300000, 0.000000 }, LightSources = LIGHTS; }, @@ -116,8 +118,9 @@ local OrionNebulaProplydsModel = { Opacity = 1.0, DisableFaceCulling = false, SpecularIntensity = 0.0, - AmbientIntensity = 1.0, + AmbientIntensity = 0.0, DiffuseIntensity = 1.0, + --PerformShading = false, RotationVector = { 0.000000, 22.300000, 0.000000 }, LightSources = LIGHTS; }, diff --git a/modules/base/lightsource/scenegraphlightsource.cpp b/modules/base/lightsource/scenegraphlightsource.cpp index 50f6695d5b..ef59e739e7 100644 --- a/modules/base/lightsource/scenegraphlightsource.cpp +++ b/modules/base/lightsource/scenegraphlightsource.cpp @@ -137,11 +137,10 @@ glm::vec3 SceneGraphLightSource::directionViewSpace(const RenderData& renderData const glm::dvec3 renderNodePosition = renderData.modelTransform.translation; - const glm::dvec3 lightDirectionViewSpace = renderData.camera.viewRotationMatrix() * - glm::dvec4((lightPosition - renderNodePosition), 1.0); + const glm::dvec3 viewSpace = glm::dvec3(renderData.camera.combinedViewMatrix() * + glm::dvec4((lightPosition - renderNodePosition), 1.0)); - return glm::normalize(lightDirectionViewSpace); + return glm::normalize(viewSpace); } - } // namespace openspace diff --git a/modules/base/rendering/renderablemodel.cpp b/modules/base/rendering/renderablemodel.cpp index 0aa9bb2273..39c920ded5 100644 --- a/modules/base/rendering/renderablemodel.cpp +++ b/modules/base/rendering/renderablemodel.cpp @@ -34,11 +34,11 @@ #include #include #include - #include #include #include #include +#include #include #include @@ -46,11 +46,25 @@ namespace { constexpr const char* ProgramName = "ModelProgram"; constexpr const char* KeyGeometry = "Geometry"; - constexpr const std::array UniformNames = { + constexpr const int DefaultBlending = 0; + constexpr const int AdditiveBlending = 1; + constexpr const int PointsAndLinesBlending = 2; + constexpr const int PolygonBlending = 3; + constexpr const int ColorAddingBlending = 4; + + std::map BlendingMapping = { + { "Default", DefaultBlending }, + { "Additive", AdditiveBlending }, + { "Points and Lines", PointsAndLinesBlending }, + { "Polygon", PolygonBlending }, + { "Color Adding", ColorAddingBlending } + }; + + constexpr const std::array UniformNames = { "opacity", "nLightSources", "lightDirectionsViewSpace", "lightIntensities", - "modelViewTransform", "crippedModelViewTransform", "projectionTransform", + "modelViewTransform", "normalTransform", "projectionTransform", "performShading", "texture1", "ambientIntensity", "diffuseIntensity", - "specularIntensity" + "specularIntensity", "opacityBlending" }; constexpr openspace::properties::Property::PropertyInfo AmbientIntensityInfo = { @@ -102,6 +116,24 @@ namespace { "Light Sources", "A list of light sources that this model should accept light from." }; + + constexpr openspace::properties::Property::PropertyInfo DisableDepthTestInfo = { + "DisableDepthTest", + "Disable Depth Test", + "Disable Depth Testing for the Model." + }; + + constexpr openspace::properties::Property::PropertyInfo BlendingOptionInfo = { + "BledingOption", + "Blending Options", + "Debug option for blending colors." + }; + + constexpr openspace::properties::Property::PropertyInfo EnableOpacityBlendingInfo = { + "EnableOpacityBlending", + "Enable Opacity Blending", + "Enable Opacity Blending." + }; } // namespace namespace openspace { @@ -177,7 +209,25 @@ documentation::Documentation RenderableModel::Documentation() { }), Optional::Yes, LightSourcesInfo.description - } + }, + { + DisableDepthTestInfo.identifier, + new BoolVerifier, + Optional::Yes, + DisableDepthTestInfo.description + }, + { + BlendingOptionInfo.identifier, + new StringVerifier, + Optional::Yes, + BlendingOptionInfo.description + }, + { + EnableOpacityBlendingInfo.identifier, + new BoolVerifier, + Optional::Yes, + EnableOpacityBlendingInfo.description + }, } }; } @@ -196,6 +246,12 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) glm::dmat3(1.0) ) , _rotationVec(RotationVecInfo, glm::dvec3(0.0), glm::dvec3(0.0), glm::dvec3(360.0)) + , _enableOpacityBlending(EnableOpacityBlendingInfo, false) + , _disableDepthTest(DisableDepthTestInfo, false) + , _blendingFuncOption( + BlendingOptionInfo, + properties::OptionProperty::DisplayType::Dropdown + ) , _lightSourcePropertyOwner({ "LightSources", "Light Sources" }) { documentation::testSpecificationAndThrow( @@ -235,6 +291,10 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) _performShading = dictionary.value(ShadingInfo.identifier); } + if (dictionary.hasKey(DisableDepthTestInfo.identifier)) { + _disableDepthTest = dictionary.value(DisableDepthTestInfo.identifier); + } + if (dictionary.hasKey(DisableFaceCullingInfo.identifier)) { _disableFaceCulling = dictionary.value(DisableFaceCullingInfo.identifier); } @@ -252,14 +312,13 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) } } - addPropertySubOwner(_lightSourcePropertyOwner); - addProperty(_ambientIntensity); addProperty(_diffuseIntensity); addProperty(_specularIntensity); addProperty(_performShading); addProperty(_disableFaceCulling); + addProperty(_disableDepthTest); addProperty(_modelTransform); addProperty(_rotationVec); @@ -271,6 +330,29 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) if (dictionary.hasKey(RotationVecInfo.identifier)) { _rotationVec = dictionary.value(RotationVecInfo.identifier); } + + _blendingFuncOption.addOption(DefaultBlending, "Default"); + _blendingFuncOption.addOption(AdditiveBlending, "Additive"); + _blendingFuncOption.addOption(PointsAndLinesBlending, "Points and Lines"); + _blendingFuncOption.addOption(PolygonBlending, "Polygon"); + _blendingFuncOption.addOption(ColorAddingBlending, "Color Adding"); + + addProperty(_blendingFuncOption); + + if (dictionary.hasKey(BlendingOptionInfo.identifier)) { + const std::string blendingOpt = dictionary.value( + BlendingOptionInfo.identifier + ); + _blendingFuncOption.set(BlendingMapping[blendingOpt]); + } + + if (dictionary.hasKey(DisableDepthTestInfo.identifier)) { + _enableOpacityBlending = dictionary.value( + EnableOpacityBlendingInfo.identifier + ); + } + + addProperty(_enableOpacityBlending); } bool RenderableModel::isReady() const { @@ -369,13 +451,11 @@ void RenderableModel::render(const RenderData& data, RendererTasks&) { glm::mat4(modelViewTransform) ); - glm::dmat4 crippedModelViewTransform = glm::transpose(glm::inverse( - glm::dmat4(glm::inverse(data.camera.sgctInternal.viewMatrix())) * modelViewTransform - )); + glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform)); _program->setUniform( - _uniformCache.crippedModelViewTransform, - glm::mat4(crippedModelViewTransform) + _uniformCache.normalTransform, + glm::mat4(normalTransform) ); _program->setUniform( @@ -386,11 +466,35 @@ void RenderableModel::render(const RenderData& data, RendererTasks&) { _program->setUniform(_uniformCache.diffuseIntensity, _diffuseIntensity); _program->setUniform(_uniformCache.specularIntensity, _specularIntensity); _program->setUniform(_uniformCache.performShading, _performShading); + _program->setUniform(_uniformCache.opacityBlending, _enableOpacityBlending); if (_disableFaceCulling) { glDisable(GL_CULL_FACE); } + glEnablei(GL_BLEND, 0); + switch (_blendingFuncOption) { + case DefaultBlending: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case AdditiveBlending: + glBlendFunc(GL_ONE, GL_ONE); + break; + case PointsAndLinesBlending: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case PolygonBlending: + glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE); + break; + case ColorAddingBlending: + glBlendFunc(GL_SRC_COLOR, GL_DST_COLOR); + break; + }; + + if (_disableDepthTest) { + glDisable(GL_DEPTH_TEST); + } + ghoul::opengl::TextureUnit unit; unit.activate(); _program->setUniform(_uniformCache.texture, unit); @@ -403,6 +507,12 @@ void RenderableModel::render(const RenderData& data, RendererTasks&) { glEnable(GL_CULL_FACE); } + global::renderEngine->openglStateCache().resetBlendState(); + + if (_disableDepthTest) { + glEnable(GL_DEPTH_TEST); + } + _program->deactivate(); } diff --git a/modules/base/rendering/renderablemodel.h b/modules/base/rendering/renderablemodel.h index 721de33f44..23beb352cc 100644 --- a/modules/base/rendering/renderablemodel.h +++ b/modules/base/rendering/renderablemodel.h @@ -26,7 +26,8 @@ #define __OPENSPACE_MODULE_BASE___RENDERABLEMODEL___H__ #include - +#include +#include #include #include #include @@ -79,11 +80,15 @@ private: properties::DMat4Property _modelTransform; properties::Vec3Property _rotationVec; + properties::BoolProperty _disableDepthTest; + properties::BoolProperty _enableOpacityBlending; + properties::OptionProperty _blendingFuncOption; + ghoul::opengl::ProgramObject* _program = nullptr; UniformCache(opacity, nLightSources, lightDirectionsViewSpace, lightIntensities, - modelViewTransform, crippedModelViewTransform, projectionTransform, + modelViewTransform, normalTransform, projectionTransform, performShading, texture, ambientIntensity, diffuseIntensity, - specularIntensity) _uniformCache; + specularIntensity, opacityBlending) _uniformCache; std::vector> _lightSources; diff --git a/modules/base/shaders/model_fs.glsl b/modules/base/shaders/model_fs.glsl index 628c87b2d4..a26843ca2c 100644 --- a/modules/base/shaders/model_fs.glsl +++ b/modules/base/shaders/model_fs.glsl @@ -34,6 +34,7 @@ uniform float diffuseIntensity = 1.0; uniform float specularIntensity = 1.0; uniform bool performShading = true; +uniform bool opacityBlending = false; uniform sampler2D texture1; @@ -46,6 +47,10 @@ uniform float opacity = 1.0; const vec3 SpecularAlbedo = vec3(1.0); Fragment getFragment() { + if (opacity == 0.0) { + discard; + } + vec3 diffuseAlbedo = texture(texture1, vs_st).rgb; Fragment frag; @@ -84,12 +89,22 @@ Fragment getFragment() { frag.color.rgb = diffuseAlbedo; } - frag.color.a = opacity; + if (opacityBlending) { + // frag.color.a = opacity * (frag.color.r + frag.color.g + frag.color.b)/3.0; + frag.color.a = opacity * max(max(frag.color.r, frag.color.g), frag.color.b); + } + else { + frag.color.a = opacity; + } + + if (frag.color.a < 0.1) { + discard; + } + frag.depth = vs_screenSpaceDepth; frag.gPosition = vs_positionCameraSpace; frag.gNormal = vec4(vs_normalViewSpace, 0.0); frag.disableLDR2HDR = true; - return frag; -} +} \ No newline at end of file diff --git a/modules/base/shaders/model_vs.glsl b/modules/base/shaders/model_vs.glsl index 8b55a1a7ba..51350b218d 100644 --- a/modules/base/shaders/model_vs.glsl +++ b/modules/base/shaders/model_vs.glsl @@ -37,7 +37,7 @@ out vec4 vs_positionCameraSpace; uniform mat4 modelViewTransform; uniform mat4 projectionTransform; -uniform mat4 crippedModelViewTransform; +uniform mat4 normalTransform; void main() { vs_positionCameraSpace = modelViewTransform * in_position; @@ -48,6 +48,5 @@ void main() { vs_st = in_st; vs_screenSpaceDepth = positionScreenSpace.w; - //vs_normalViewSpace = normalize(transpose(inverse(mat3(crippedModelViewTransform))) * in_normal); - vs_normalViewSpace = normalize(mat3(crippedModelViewTransform) * in_normal); + vs_normalViewSpace = normalize(mat3(normalTransform) * in_normal); } diff --git a/modules/space/translation/horizonstranslation.cpp b/modules/space/translation/horizonstranslation.cpp index e8cf08330f..b931afd0e3 100644 --- a/modules/space/translation/horizonstranslation.cpp +++ b/modules/space/translation/horizonstranslation.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -83,7 +84,7 @@ HorizonsTranslation::HorizonsTranslation() requireUpdate(); notifyObservers(); }); - readHorizonsTextFile(_horizonsTextFile); + loadData(); }); } @@ -99,9 +100,6 @@ HorizonsTranslation::HorizonsTranslation(const ghoul::Dictionary& dictionary) _horizonsTextFile = absPath( dictionary.value(HorizonsTextFileInfo.identifier) ); - - // Read specified file and store it in memory. - readHorizonsTextFile(_horizonsTextFile); } glm::dvec3 HorizonsTranslation::position(const UpdateData& data) const { @@ -130,12 +128,48 @@ glm::dvec3 HorizonsTranslation::position(const UpdateData& data) const { return interpolatedPos; } -void HorizonsTranslation::readHorizonsTextFile(const std::string& horizonsTextFilePath) { - std::ifstream fileStream(horizonsTextFilePath); +void HorizonsTranslation::loadData() { + std::string file = _horizonsTextFile; + if (!FileSys.fileExists(absPath(file))) { + return; + } + + std::string cachedFile = FileSys.cacheManager()->cachedFilename( + file, + ghoul::filesystem::CacheManager::Persistent::Yes + ); + + bool hasCachedFile = FileSys.fileExists(cachedFile); + if (hasCachedFile) { + LINFO(fmt::format("Cached file '{}' used for Horizon file '{}'", cachedFile, file)); + + bool success = loadCachedFile(cachedFile); + if (success) { + return; + } + else { + FileSys.cacheManager()->removeCacheFile(file); + // Intentional fall-through to the 'else' computation to generate the cache + // file for the next run + } + } + else { + LINFO(fmt::format("Cache for Horizon file '{}' not found", file)); + } + LINFO(fmt::format("Loading Horizon file '{}'", file)); + + readHorizonsTextFile(); + + LINFO("Saving cache"); + saveCachedFile(cachedFile); +} + +void HorizonsTranslation::readHorizonsTextFile() { + std::ifstream fileStream(_horizonsTextFile); if (!fileStream.good()) { LERROR(fmt::format( - "Failed to open Horizons text file '{}'", horizonsTextFilePath + "Failed to open Horizons text file '{}'", _horizonsTextFile )); return; } @@ -186,4 +220,81 @@ void HorizonsTranslation::readHorizonsTextFile(const std::string& horizonsTextFi fileStream.close(); } +bool HorizonsTranslation::loadCachedFile(const std::string& file) { + std::ifstream fileStream(file, std::ifstream::binary); + + if (!fileStream.good()) { + LERROR(fmt::format("Error opening file '{}' for loading cache file", file)); + return false; + } + + // Read how many keyframes to read + int32_t nKeyframes = 0; + + fileStream.read(reinterpret_cast(&nKeyframes), sizeof(int32_t)); + if (nKeyframes == 0) { + throw ghoul::RuntimeError("Error reading cache: No values were loaded"); + } + + // Read the values in same order as they were written + for (int i = 0; i < nKeyframes; ++i) { + double timestamp, x, y, z; + glm::dvec3 gPos; + + // Timestamp + fileStream.read(reinterpret_cast(×tamp), sizeof(double)); + + // Position vector components + fileStream.read(reinterpret_cast(&x), sizeof(double)); + fileStream.read(reinterpret_cast(&y), sizeof(double)); + fileStream.read(reinterpret_cast(&z), sizeof(double)); + + // Recreate the position vector + gPos = glm::dvec3(x, y, z); + + // Add keyframe in timeline + _timeline.addKeyframe(timestamp, std::move(gPos)); + } + + return fileStream.good(); +} + +void HorizonsTranslation::saveCachedFile(const std::string& file) const { + std::ofstream fileStream(file, std::ofstream::binary); + if (!fileStream.good()) { + LERROR(fmt::format("Error opening file '{}' for save cache file", file)); + return; + } + + // Write how many keyframes are to be written + int32_t nKeyframes = static_cast(_timeline.nKeyframes()); + if (nKeyframes == 0) { + throw ghoul::RuntimeError("Error writing cache: No values were loaded"); + } + fileStream.write(reinterpret_cast(&nKeyframes), sizeof(int32_t)); + + // Write data + std::deque> keyframes = _timeline.keyframes(); + for (int i = 0; i < nKeyframes; i++) { + // First write timestamp + fileStream.write(reinterpret_cast( + &keyframes[i].timestamp), + sizeof(double) + ); + + // and then the components of the position vector one by one + fileStream.write(reinterpret_cast( + &keyframes[i].data.x), + sizeof(double) + ); + fileStream.write(reinterpret_cast( + &keyframes[i].data.y), + sizeof(double) + ); + fileStream.write(reinterpret_cast( + &keyframes[i].data.z), + sizeof(double) + ); + } +} } // namespace openspace diff --git a/modules/space/translation/horizonstranslation.h b/modules/space/translation/horizonstranslation.h index a0c2a9fea8..f996373083 100644 --- a/modules/space/translation/horizonstranslation.h +++ b/modules/space/translation/horizonstranslation.h @@ -59,7 +59,10 @@ public: static documentation::Documentation Documentation(); private: - void readHorizonsTextFile(const std::string& _horizonsTextFilePath); + void loadData(); + void readHorizonsTextFile(); + bool loadCachedFile(const std::string& file); + void saveCachedFile(const std::string& file) const; properties::StringProperty _horizonsTextFile; std::unique_ptr _fileHandle; diff --git a/modules/touch/src/win32_touch.cpp b/modules/touch/src/win32_touch.cpp index 3e1640af86..bfd8b73aa9 100644 --- a/modules/touch/src/win32_touch.cpp +++ b/modules/touch/src/win32_touch.cpp @@ -35,6 +35,7 @@ #include #include #include +#include // #define ENABLE_TUIOMESSAGES #define ENABLE_DIRECTMSG @@ -230,25 +231,31 @@ Win32TouchHook::Win32TouchHook(void* nativeWindow) { GetCurrentThreadId() ); - // In theory, if our UI is pumped from a different thread, we can - // handle Low-level mouse events in that thread as well. - // this might help reduce mouse lag while running OpenSpace? - // gMouseHookThread = new std::thread([](){ - // gMouseHook = SetWindowsHookExW( - // WH_MOUSE_LL, - // LowLevelMouseProc, - // GetModuleHandleW(NULL), - // 0 //<- Global thread id (low-level mouse is global only) - // ); - // if (!gMouseHook) { - // LINFO("Could not setup mousehook!"); - // } + // Attach a lowlevel mouse hook. + // This mouse hook prevents injected mouse events (touch-to-mouse), + // since we cannot catch it in our messageloop. + // Since this is attached to windows global thread, this will block lowlevel mouse + // access to all running applications if we stall in this thread. + // Debug breakpoints typically freeze our application, in which case we simply don't + // create this if a debugger is attached. + if (!IsDebuggerPresent()) { + gMouseHookThread = new std::thread([](){ + gMouseHook = SetWindowsHookExW( + WH_MOUSE_LL, + LowLevelMouseProc, + GetModuleHandleW(NULL), + 0 //<- Global thread id (low-level mouse is global only) + ); + if (!gMouseHook) { + LINFO("Could not setup mousehook!"); + } - // MSG msg; - // while (GetMessage(&msg, NULL, 0, 0)) { - // DispatchMessage(&msg); - // } - // }); + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + DispatchMessage(&msg); + } + }); + } if (!gTouchHook) { LINFO(fmt::format("Failed to setup WindowsHook for touch input redirection")); @@ -272,13 +279,13 @@ Win32TouchHook::~Win32TouchHook() { } // Low-level mouse hook is "needed" if we want to stop mousecursor from moving -// when we get a touch-input on our window A negative effect is that this -// function is for global threads, meaning our application will cause Windows to -// stall the mouse cursor when this function can't be scheduled. This is not yet -// fail-proof...might be a race-condition on message pumping? +// when we get a touch-input on our window. +// A negative effect is that this function is for global threads, meaning our +// application will cause Windows to stall the mouse cursor when this +// function can't be scheduled (i.e. when debugger hits a breakpoint). +// This is not yet fail-proof...might be a race-condition on message pumping? // - Seems to move the cursor when we get two fingers as input.. -// - If we ourselves would pump windows for events, we can handle this in the -// pump-loop +// - If we ourselves would pump windows for events, we can handle this. LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { constexpr const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00; constexpr const LONG_PTR MOUSEEVENTF_FROMTOUCH = 0xFF515700;