From 93e8e11cf75ae1d0a3265198feeb578855487f88 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Thu, 18 Feb 2016 17:21:01 +0100 Subject: [PATCH] Texture browser --HG-- branch : feature-material-editor --- code/nel/tools/3d/mesh_editor/main_window.cpp | 7 + .../tools/3d/mesh_editor/texture_browser.cpp | 138 +++++++++ .../tools/3d/mesh_editor/texture_browser.h | 70 +++++ code/nel/tools/3d/shared_widgets/event_loop.h | 281 ++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 code/nel/tools/3d/mesh_editor/texture_browser.cpp create mode 100644 code/nel/tools/3d/mesh_editor/texture_browser.h create mode 100644 code/nel/tools/3d/shared_widgets/event_loop.h diff --git a/code/nel/tools/3d/mesh_editor/main_window.cpp b/code/nel/tools/3d/mesh_editor/main_window.cpp index e9ec2d430..f7e1502ec 100644 --- a/code/nel/tools/3d/mesh_editor/main_window.cpp +++ b/code/nel/tools/3d/mesh_editor/main_window.cpp @@ -43,6 +43,7 @@ #include "../shared_widgets/error_list.h" #include "graphics_viewport.h" #include "graphics_config.h" +#include "texture_browser.h" using namespace std; using namespace NLMISC; @@ -103,6 +104,12 @@ CMainWindow::CMainWindow(QWidget *parent, Qt::WindowFlags flags) } } } + + QDockWidget *dock = new QDockWidget(this); + dock->setFloating(true); + CTextureBrowser *browser = new CTextureBrowser(dock); + browser->resize(800, 800); + dock->setWidget(browser); } CMainWindow::~CMainWindow() diff --git a/code/nel/tools/3d/mesh_editor/texture_browser.cpp b/code/nel/tools/3d/mesh_editor/texture_browser.cpp new file mode 100644 index 000000000..6f4418cdb --- /dev/null +++ b/code/nel/tools/3d/mesh_editor/texture_browser.cpp @@ -0,0 +1,138 @@ +// NeL - MMORPG Framework +// Copyright (C) 2016 Winch Gate Property Limited +// Author: Jan Boon +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include +#include "texture_browser.h" + +// STL includes +#include + +// Qt includes +#include +#include +#include +#include +#include +#include + +// NeL includes +// #include +#include +#include +#include +#include + +// Project includes +#include "../shared_widgets/event_loop.h" + +// See also: studio/.../gui_editor/texture_chooser.cpp + +// UTILITY -> +QPixmap qPixmapFromCBitmap(NLMISC::CBitmap &bitmap, bool alpha) +{ + QImage img(bitmap.getWidth(), bitmap.getHeight(), alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32); + NLMISC::CObjectVector &pixels = bitmap.getPixels(); + + uint height = bitmap.getHeight(); + uint stride = bitmap.getWidth() * sizeof(NLMISC::CRGBA); + for (uint y = 0; y < height; ++y) + { + // memcpy(img.scanLine(y), &pixels[y * stride], stride); + // Convert from ABGR to ARGB + uint8 *dst = img.scanLine(y); + uint8 *src = &pixels[y * stride]; + for (uint x = 0; x < stride; x += 4) + { + dst[x] = src[x + 2]; + dst[x + 1] = src[x + 1]; + dst[x + 2] = src[x]; + dst[x + 3] = src[x + 3]; + } + } + + return QPixmap::fromImage(img); +} +// <- UTILITY + +CTextureBrowser::CTextureBrowser(QWidget *parent) : QListWidget(parent) +{ + qRegisterMetaType("CStdFunctionVoid"); + m_Thread = new CEventLoop(); + m_Thread->run(); + + setViewMode(QListWidget::IconMode); + setIconSize(QSize(200, 200)); + setResizeMode(QListWidget::Adjust); + + setDirectory("W:/database/stuff/fyros/agents/_textures/actors/"); +} + +CTextureBrowser::~CTextureBrowser() +{ + m_Thread->clear(); + delete m_Thread; +} + +void CTextureBrowser::setDirectory(const QString &dir) +{ + // Remove any pending stuff + m_Thread->clear(); + + // Sync up, clear, and start processing + m_Thread->immediate([this, dir]() -> void { + invokeStdFunction([this, dir]() -> void { + // m_listeWidget->addItem(new QListWidgetItem(QIcon("../earth.jpg"), "Earth")); + clear(); + std::vector files; + NLMISC::CPath::getPathContent(dir.toUtf8().data(), false, false, true, files); + for (size_t i = 0; i < files.size(); ++i) + { + std::string &file = files[i]; + m_Thread->immediate([this, file]() -> void { + std::string ext = NLMISC::toLower(NLMISC::CFile::getExtension(file)); + if (ext == "dds" || ext == "tga" || ext == "png" || ext == "jpg" || ext == "jpeg") + { + NLMISC::CIFile f; + if (f.open(file)) + { + NLMISC::CBitmap bitmap; + bitmap.load(f); + bitmap.resample(128, 128); + QPixmap pixmap = qPixmapFromCBitmap(bitmap, false); + QString fileName = QFileInfo(QString::fromUtf8(file.c_str())).fileName(); + invokeStdFunction([this, pixmap, fileName]() -> void { + addItem(new QListWidgetItem(QIcon(pixmap), fileName)); + }); + } + } + }); + } + }); + }); +} + +void CTextureBrowser::invokeStdFunction(CStdFunctionVoid f) +{ + QMetaObject::invokeMethod(this, "callStdFunction", Qt::QueuedConnection, Q_ARG(CStdFunctionVoid, f)); +} + +void CTextureBrowser::callStdFunction(CStdFunctionVoid f) +{ + f(); +} + +/* end of file */ diff --git a/code/nel/tools/3d/mesh_editor/texture_browser.h b/code/nel/tools/3d/mesh_editor/texture_browser.h new file mode 100644 index 000000000..9df6333e6 --- /dev/null +++ b/code/nel/tools/3d/mesh_editor/texture_browser.h @@ -0,0 +1,70 @@ +// NeL - MMORPG Framework +// Copyright (C) 2016 Winch Gate Property Limited +// Author: Jan Boon +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef NL_TEXTURE_BROWSER_H +#define NL_TEXTURE_BROWSER_H +#include + +// STL includes +#include + +// Qt includes +#include + +// NeL includes +// ... + +class CEventLoop; +typedef std::function CStdFunctionVoid; + +/** + * CTextureBrowser + * \brief CTextureBrowser + * \date 2016-02-18 14:06GMT + * \author Jan Boon + */ +class CTextureBrowser : public QListWidget +{ + Q_OBJECT + +public: + CTextureBrowser(QWidget *parent = NULL); + virtual ~CTextureBrowser(); + + +private: + void setDirectory(const QString &dir); + + // STD INVOKE -> +public: + void invokeStdFunction(CStdFunctionVoid f); +private slots: + void callStdFunction(CStdFunctionVoid f); + // <- STD INVOKE + +private: + CEventLoop *m_Thread; + +private: + CTextureBrowser(const CTextureBrowser &); + CTextureBrowser &operator=(const CTextureBrowser &); + +}; /* class CTextureBrowser */ + +#endif /* #ifndef NL_TEXTURE_BROWSER_H */ + +/* end of file */ diff --git a/code/nel/tools/3d/shared_widgets/event_loop.h b/code/nel/tools/3d/shared_widgets/event_loop.h new file mode 100644 index 000000000..1f6661c74 --- /dev/null +++ b/code/nel/tools/3d/shared_widgets/event_loop.h @@ -0,0 +1,281 @@ +/* + +Copyright (C) 2016 by authors +Author: Jan Boon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +// Source: https://github.com/kaetemi/threadutil/blob/master/threadutil/eventloop.h + +#ifndef NL_EVENT_LOOP_H +#define NL_EVENT_LOOP_H + +#include +#include +#include + +#include + +#include +#include + +class CEventLoop +{ +public: + CEventLoop() : m_Running(false), m_Handle(0) + { + + } + + ~CEventLoop() + { + stop(); + clear(); + } + + void run() + { + stop(); + m_Running = true; + m_Thread = std::move(std::thread(&CEventLoop::loop, this)); + } + + void runSync() + { + stop(); + m_Running = true; + loop(); + } + + void stop() // thread-safe + { + m_Running = false; + poke(); + if (m_Thread.joinable()) + m_Thread.join(); + } + + void clear() // thread-safe + { + std::unique_lock lock(m_QueueLock); + std::unique_lock tlock(m_QueueTimeoutLock); + m_Immediate = std::move(std::queue>()); + m_Timeout = std::move(std::priority_queue()); + } + + void clear(int handle) // thread-safe, relatively slow, not recommended nor reliable to do this for timeouts, only reliable for intervals + { + std::unique_lock lock(m_QueueTimeoutLock); + std::priority_queue timeout; + while (m_Timeout.size()) + { + if (m_Timeout.top().handle != handle) + timeout.push(m_Timeout.top()); + m_Timeout.pop(); + } + m_Timeout = std::move(timeout); + } + + void join() // thread-safe + { + std::mutex syncLock; + std::condition_variable syncCond; + std::unique_lock lock(syncLock); + immediate([this, &syncCond]() -> void { + syncCond.notify_one(); + }); + m_PokeCond.wait(lock); + } + +public: + void immediate(std::function f) // thread-safe + { + std::unique_lock lock(m_QueueLock); + m_Immediate.push(f); + poke(); + } + + template + int timeout(std::function f, const std::chrono::duration& delta) // thread-safe + { + timeout_func tf; + tf.f = f; + tf.time = std::chrono::steady_clock::now() + delta; + tf.interval = std::chrono::nanoseconds::zero(); + tf.handle = ++m_Handle; + ; { + std::unique_lock lock(m_QueueTimeoutLock); + m_Timeout.push(tf); + poke(); + } + return tf.handle; + } + + template + int interval(std::function f, const std::chrono::duration& interval) // thread-safe + { + timeout_func tf; + tf.f = f; + tf.time = std::chrono::steady_clock::now() + interval; + tf.interval = interval; + tf.handle = ++m_Handle; + ; { + std::unique_lock lock(m_QueueTimeoutLock); + m_Timeout.push(tf); + poke(); + } + return tf.handle; + } + + int timed(std::function f, const std::chrono::steady_clock::time_point &point) // thread-safe + { + timeout_func tf; + tf.f = f; + tf.time = point; + tf.interval = std::chrono::steady_clock::duration::zero(); + tf.handle = ++m_Handle; + ; { + std::unique_lock lock(m_QueueTimeoutLock); + m_Timeout.push(tf); + poke(); + } + return tf.handle; + } + +public: + void thread(std::function f, std::function callback) + { + std::thread t([this, f, callback]() -> void { + f(); + immediate(callback); + }); + t.detach(); + } + +private: + void loop() + { + while (m_Running) + { + m_Poked = false; + + for (;;) + { + m_QueueLock.lock(); + if (!m_Immediate.size()) + { + m_QueueLock.unlock(); + break; + } + std::function f = m_Immediate.front(); + m_Immediate.pop(); + m_QueueLock.unlock(); + f(); + } + + bool poked = false; + for (;;) + { + m_QueueTimeoutLock.lock(); + if (!m_Timeout.size()) + { + m_QueueTimeoutLock.unlock(); + break; + } + const timeout_func &tfr = m_Timeout.top(); + if (tfr.time > std::chrono::steady_clock::now()) // wait + { + m_QueueTimeoutLock.unlock(); + ; { + std::unique_lock lock(m_PokeLock); + if (!m_Poked) + m_PokeCond.wait_until(lock, tfr.time); + } + poked = true; + break; + } + timeout_func tf = tfr; + m_Timeout.pop(); + m_QueueTimeoutLock.unlock(); + tf.f(); // call + if (tf.interval > std::chrono::nanoseconds::zero()) // repeat + { + tf.time += tf.interval; + ; { + std::unique_lock lock(m_QueueTimeoutLock); + m_Timeout.push(tf); + poke(); + } + } + } + + if (!poked) + { + std::unique_lock lock(m_PokeLock); + if (!m_Poked) + m_PokeCond.wait(lock); + } + } + } + + void poke() // private + { + std::unique_lock lock(m_PokeLock); + m_PokeCond.notify_one(); + m_Poked = true; + } + +private: + struct timeout_func + { + std::function f; + std::chrono::steady_clock::time_point time; + std::chrono::steady_clock::duration interval; + int handle; + + bool operator <(const timeout_func &o) const + { + return time > o.time; + } + + }; + +private: + volatile bool m_Running; + volatile bool m_Poked; + std::thread m_Thread; + std::mutex m_PokeLock; + std::condition_variable m_PokeCond; + + std::mutex m_QueueLock; + std::queue> m_Immediate; + std::mutex m_QueueTimeoutLock; + std::priority_queue m_Timeout; + int m_Handle; + +}; + +#endif /* NL_EVENT_LOOP_H */ + +/* end of file */