mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-06 11:39:49 -06:00
Merge remote-tracking branch 'origin/master' into issue/2029
# Conflicts: # modules/skybrowser/src/wwtcommunicator.cpp
This commit is contained in:
@@ -38,7 +38,7 @@ This repository contains the source code and example profiles for OpenSpace, but
|
||||
|
||||
Requirements for compiling are:
|
||||
- CMake version 3.10 or above
|
||||
- C++ compiler supporting C++17 (MSVC 16.10, GCC9, Clang10)
|
||||
- C++ compiler supporting C++20 (MSVC 19.31, GCC11, Clang14, AppleClang 13.1.6)
|
||||
- [Boost](http://www.boost.org/)
|
||||
- [Qt](http://www.qt.io/download)
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ public:
|
||||
void setRenderBin(RenderBin bin);
|
||||
bool matchesRenderBinMask(int binMask);
|
||||
|
||||
void setFade(float fade);
|
||||
|
||||
bool isVisible() const;
|
||||
|
||||
void onEnabledChange(std::function<void(bool)> callback);
|
||||
|
||||
@@ -98,8 +98,8 @@ public:
|
||||
|
||||
void addScreenSpaceRenderable(std::unique_ptr<ScreenSpaceRenderable> s);
|
||||
void removeScreenSpaceRenderable(ScreenSpaceRenderable* s);
|
||||
void removeScreenSpaceRenderable(const std::string& identifier);
|
||||
ScreenSpaceRenderable* screenSpaceRenderable(const std::string& identifier);
|
||||
void removeScreenSpaceRenderable(std::string_view identifier);
|
||||
ScreenSpaceRenderable* screenSpaceRenderable(std::string_view identifier);
|
||||
std::vector<ScreenSpaceRenderable*> screenSpaceRenderables() const;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> buildRenderProgram(
|
||||
|
||||
@@ -101,7 +101,7 @@ void SkyBrowserTopic::sendBrowserData() {
|
||||
|
||||
// Pass data for all the browsers and the corresponding targets
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
ghoul::Dictionary targets;
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include <modules/webbrowser/include/webrenderhandler.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
#include <openspace/properties/triggerproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (push)
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
explicit Browser(const ghoul::Dictionary& dictionary);
|
||||
virtual ~Browser();
|
||||
|
||||
bool initializeGL();
|
||||
void initializeGL();
|
||||
void deinitializeGL();
|
||||
bool isReady() const;
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
void setCallbackDimensions(const std::function<void(const glm::dvec2&)>& function);
|
||||
|
||||
protected:
|
||||
properties::Vec2Property _browserPixeldimensions;
|
||||
properties::Vec2Property _browserDimensions;
|
||||
properties::StringProperty _url;
|
||||
properties::TriggerProperty _reload;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
|
||||
namespace openspace::documentation { struct Documentation; }
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include <modules/skybrowser/include/wwtcommunicator.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___TARGETBROWSERPAIR___H__
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <deque>
|
||||
|
||||
namespace ghoul { class Dictionary; }
|
||||
|
||||
@@ -42,7 +40,6 @@ class ScreenSpaceRenderable;
|
||||
class TargetBrowserPair {
|
||||
public:
|
||||
TargetBrowserPair(SceneGraphNode* target, ScreenSpaceSkyBrowser* browser);
|
||||
TargetBrowserPair& operator=(TargetBrowserPair other);
|
||||
|
||||
// Target & Browser
|
||||
void initialize();
|
||||
@@ -60,9 +57,7 @@ public:
|
||||
|
||||
// Browser
|
||||
void sendIdToBrowser() const;
|
||||
void updateBrowserSize();
|
||||
std::vector<std::pair<std::string, glm::dvec3>> displayCopies() const;
|
||||
bool isImageCollectionLoaded();
|
||||
|
||||
// Target
|
||||
void centerTargetOnScreen();
|
||||
@@ -73,7 +68,6 @@ public:
|
||||
bool isEnabled() const;
|
||||
|
||||
void setEnabled(bool enable);
|
||||
void setOpacity(float opacity);
|
||||
void setVerticalFov(double vfov);
|
||||
void setEquatorialAim(const glm::dvec2& aim);
|
||||
void setBorderColor(const glm::ivec3& color);
|
||||
@@ -90,9 +84,7 @@ public:
|
||||
std::string browserId() const;
|
||||
std::string targetRenderableId() const;
|
||||
std::string targetNodeId() const;
|
||||
float browserRatio() const;
|
||||
|
||||
SceneGraphNode* targetNode() const;
|
||||
ScreenSpaceSkyBrowser* browser() const;
|
||||
std::vector<int> selectedImages() const;
|
||||
|
||||
@@ -107,12 +99,7 @@ public:
|
||||
void setImageOpacity(int i, float opacity);
|
||||
void hideChromeInterface();
|
||||
|
||||
friend bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs);
|
||||
friend bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs);
|
||||
|
||||
private:
|
||||
void aimTargetGalactic(glm::dvec3 direction);
|
||||
|
||||
// Target and browser
|
||||
RenderableSkyTarget* _targetRenderable = nullptr;
|
||||
ScreenSpaceSkyBrowser* _browser = nullptr;
|
||||
|
||||
@@ -33,20 +33,7 @@ namespace openspace::skybrowser {
|
||||
|
||||
// Constants
|
||||
constexpr double ScreenSpaceZ = -2.1;
|
||||
constexpr glm::dvec3 NorthPole = { 0.0, 0.0, 1.0 };
|
||||
constexpr double CelestialSphereRadius = 4 * distanceconstants::Parsec;
|
||||
|
||||
// Conversion matrix - J2000 equatorial <-> galactic
|
||||
// https://arxiv.org/abs/1010.3773v1
|
||||
const glm::dmat3 conversionMatrix = glm::dmat3(
|
||||
-0.054875539390, 0.494109453633, -0.867666135681, // col 0
|
||||
-0.873437104725, -0.444829594298, -0.198076389622, // col 1
|
||||
-0.483834991775, 0.746982248696, 0.455983794523 // col 2
|
||||
);
|
||||
|
||||
// Galactic coordinates are projected onto the celestial sphere
|
||||
// Equatorial coordinates are unit length
|
||||
// Conversion spherical <-> Cartesian
|
||||
constexpr double CelestialSphereRadius = 4.0 * distanceconstants::Parsec;
|
||||
|
||||
/**
|
||||
* Converts from Cartesian coordinates to spherical coordinates with unit length.
|
||||
@@ -205,9 +192,8 @@ public:
|
||||
Animation(T start, T goal, double time)
|
||||
: _goal(std::move(goal))
|
||||
, _start(std::move(start))
|
||||
{
|
||||
_animationTime = std::chrono::milliseconds(static_cast<int>(time * 1000));
|
||||
}
|
||||
, _animationTime(std::chrono::milliseconds(static_cast<int>(time * 1000)))
|
||||
{}
|
||||
|
||||
void start() {
|
||||
_isStarted = true;
|
||||
@@ -219,12 +205,12 @@ public:
|
||||
}
|
||||
|
||||
bool isAnimating() const {
|
||||
bool timeLeft = timeSpent().count() < _animationTime.count() ? true : false;
|
||||
bool timeLeft = timeSpent().count() < _animationTime.count();
|
||||
return timeLeft && _isStarted;
|
||||
}
|
||||
|
||||
T getNewValue();
|
||||
glm::dmat4 getRotationMatrix();
|
||||
T newValue() const;
|
||||
glm::dmat4 rotationMatrix();
|
||||
|
||||
private:
|
||||
std::chrono::duration<double, std::milli> timeSpent() const {
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
|
||||
#include <modules/skybrowser/include/browser.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/vector/dvec2property.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/ivec3property.h>
|
||||
#include <deque>
|
||||
|
||||
namespace openspace {
|
||||
@@ -38,8 +34,7 @@ namespace openspace {
|
||||
class WwtCommunicator : public Browser {
|
||||
public:
|
||||
explicit WwtCommunicator(const ghoul::Dictionary& dictionary);
|
||||
WwtCommunicator(const WwtCommunicator&) = default;
|
||||
~WwtCommunicator() override;
|
||||
~WwtCommunicator() override = default;
|
||||
|
||||
void update();
|
||||
|
||||
@@ -85,23 +80,10 @@ protected:
|
||||
std::deque<std::pair<int, double>> _selectedImages;
|
||||
|
||||
private:
|
||||
void setWebpageBorderColor(glm::ivec3 color) const;
|
||||
void sendMessageToWwt(const ghoul::Dictionary& msg) const;
|
||||
|
||||
// WorldWide Telescope messages
|
||||
ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov,
|
||||
double roll, bool shouldMoveInstantly = true) const;
|
||||
ghoul::Dictionary loadCollectionMessage(const std::string& url) const;
|
||||
ghoul::Dictionary setForegroundMessage(const std::string& name) const;
|
||||
ghoul::Dictionary addImageMessage(const std::string& id,
|
||||
const std::string& url) const;
|
||||
ghoul::Dictionary removeImageMessage(const std::string& id) const;
|
||||
ghoul::Dictionary setImageOpacityMessage(const std::string& id, double opacity) const;
|
||||
ghoul::Dictionary setLayerOrderMessage(const std::string& id, int version);
|
||||
|
||||
bool _borderColorIsDirty = false;
|
||||
bool _equatorialAimIsDirty = false;
|
||||
int messageCounter = 0;
|
||||
|
||||
// Time variables
|
||||
// For capping the message passing to WWT
|
||||
|
||||
@@ -25,51 +25,22 @@
|
||||
#ifndef __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__
|
||||
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsuggest-override"
|
||||
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
|
||||
#endif
|
||||
|
||||
#include <modules/skybrowser/ext/tinyxml2/tinyxml2.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace openspace::documentation { struct Documentation; }
|
||||
|
||||
namespace openspace::wwt {
|
||||
const std::string Thumbnail = "Thumbnail";
|
||||
const std::string Name = "Name";
|
||||
const std::string ImageSet = "ImageSet";
|
||||
const std::string Dec = "Dec";
|
||||
const std::string RA = "RA";
|
||||
const std::string Undefined = "";
|
||||
const std::string Folder = "Folder";
|
||||
const std::string Place = "Place";
|
||||
const std::string ThumbnailUrl = "ThumbnailUrl";
|
||||
const std::string Url = "Url";
|
||||
const std::string Credits = "Credits";
|
||||
const std::string CreditsUrl = "CreditsUrl";
|
||||
const std::string ZoomLevel = "ZoomLevel";
|
||||
const std::string DataSetType = "DataSetType";
|
||||
const std::string Sky = "Sky";
|
||||
} // namespace openspace::wwt
|
||||
namespace tinyxml2 { class XMLElement; }
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
struct ImageData {
|
||||
std::string name = wwt::Undefined;
|
||||
std::string thumbnailUrl = wwt::Undefined;
|
||||
std::string imageUrl = wwt::Undefined;
|
||||
std::string credits = wwt::Undefined;
|
||||
std::string creditsUrl = wwt::Undefined;
|
||||
std::string collection = wwt::Undefined;
|
||||
std::string name;
|
||||
std::string thumbnailUrl;
|
||||
std::string imageUrl;
|
||||
std::string credits;
|
||||
std::string creditsUrl;
|
||||
std::string collection;
|
||||
bool hasCelestialCoords = false;
|
||||
float fov = 0.f;
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
@@ -78,20 +49,15 @@ struct ImageData {
|
||||
|
||||
class WwtDataHandler {
|
||||
public:
|
||||
WwtDataHandler() = default;
|
||||
~WwtDataHandler();
|
||||
|
||||
void loadImages(const std::string& root, const std::filesystem::path& directory);
|
||||
int nLoadedImages() const;
|
||||
const ImageData& getImage(int i) const;
|
||||
const ImageData& image(int i) const;
|
||||
|
||||
private:
|
||||
void saveImageFromNode(tinyxml2::XMLElement* node, std::string collection);
|
||||
void saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection);
|
||||
void saveImagesFromXml(const tinyxml2::XMLElement* root, std::string collection);
|
||||
|
||||
// Images
|
||||
std::vector<ImageData> _images;
|
||||
std::vector<tinyxml2::XMLDocument*> _xmls;
|
||||
};
|
||||
} // namespace openspace
|
||||
|
||||
|
||||
@@ -150,8 +150,10 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
, _hideTargetsBrowsersWithGui(HideWithGuiInfo, false)
|
||||
, _inverseZoomDirection(InverseZoomInfo, false)
|
||||
, _spaceCraftAnimationTime(SpaceCraftTimeInfo, 2.0, 0.0, 10.0)
|
||||
, _wwtImageCollectionUrl(ImageCollectionInfo,
|
||||
"https://data.openspaceproject.com/wwt/1/imagecollection.wtml")
|
||||
, _wwtImageCollectionUrl(
|
||||
ImageCollectionInfo,
|
||||
"https://data.openspaceproject.com/wwt/1/imagecollection.wtml"
|
||||
)
|
||||
{
|
||||
addProperty(_enabled);
|
||||
addProperty(_showTitleInGuiBrowser);
|
||||
@@ -189,7 +191,8 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
if (vizModeChanged) {
|
||||
constexpr float FadeDuration = 2.f;
|
||||
|
||||
if (camWasInSolarSystem) { // Camera moved out of the solar system => fade out
|
||||
if (camWasInSolarSystem) {
|
||||
// Camera moved out of the solar system => fade out
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->startFading(0.f, FadeDuration);
|
||||
}
|
||||
@@ -197,7 +200,8 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
// Also hide the hover circle
|
||||
disableHoverCircle();
|
||||
}
|
||||
else { // Camera moved into the solar system => fade in
|
||||
else {
|
||||
// Camera moved into the solar system => fade in
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->startFading(1.f, FadeDuration);
|
||||
}
|
||||
@@ -246,10 +250,6 @@ void SkyBrowserModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
|
||||
// Register ScreenSpaceSkyTarget
|
||||
fRenderable->registerClass<RenderableSkyTarget>("RenderableSkyTarget");
|
||||
|
||||
// Create data handler dynamically to avoid the linking error that
|
||||
// came up when including the include file in the module header file
|
||||
_dataHandler = std::make_unique<WwtDataHandler>();
|
||||
}
|
||||
|
||||
void SkyBrowserModule::addTargetBrowserPair(const std::string& targetId,
|
||||
@@ -281,8 +281,6 @@ void SkyBrowserModule::removeTargetBrowserPair(const std::string& id) {
|
||||
_targetsBrowsers.begin(),
|
||||
_targetsBrowsers.end(),
|
||||
[&](const std::unique_ptr<TargetBrowserPair>& pair) {
|
||||
// should this be?
|
||||
// found == pair.get()
|
||||
return found == pair.get();
|
||||
}
|
||||
);
|
||||
@@ -298,6 +296,7 @@ void SkyBrowserModule::lookAtTarget(const std::string& id) {
|
||||
}
|
||||
|
||||
void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) {
|
||||
ghoul_assert(circle, "No circle specified");
|
||||
_hoverCircle = circle;
|
||||
|
||||
// Always disable it per default. It should only be visible on interaction
|
||||
@@ -305,46 +304,48 @@ void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) {
|
||||
}
|
||||
|
||||
void SkyBrowserModule::moveHoverCircle(int i, bool useScript) {
|
||||
const ImageData& image = _dataHandler->getImage(i);
|
||||
const ImageData& image = _dataHandler.image(i);
|
||||
|
||||
// Only move and show circle if the image has coordinates
|
||||
if (_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem) {
|
||||
const std::string id = _hoverCircle->identifier();
|
||||
if (!(_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the circle
|
||||
if (useScript) {
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);",
|
||||
id
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
else {
|
||||
Renderable* renderable = _hoverCircle->renderable();
|
||||
if (renderable) {
|
||||
renderable->property("Fade")->set(1.f);
|
||||
}
|
||||
}
|
||||
const std::string id = _hoverCircle->identifier();
|
||||
|
||||
// Set the exact target position
|
||||
// Move it slightly outside of the celestial sphere so it doesn't overlap with
|
||||
// the target
|
||||
glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian);
|
||||
pos *= skybrowser::CelestialSphereRadius * 1.1;
|
||||
|
||||
// Note that the position can only be set through the script engine
|
||||
// Show the circle
|
||||
if (useScript) {
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(pos)
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);",
|
||||
id
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
else {
|
||||
Renderable* renderable = _hoverCircle->renderable();
|
||||
if (renderable) {
|
||||
renderable->setFade(1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the exact target position
|
||||
// Move it slightly outside of the celestial sphere so it doesn't overlap with
|
||||
// the target
|
||||
glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian);
|
||||
pos *= skybrowser::CelestialSphereRadius * 1.1;
|
||||
|
||||
// Note that the position can only be set through the script engine
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(pos)
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void SkyBrowserModule::disableHoverCircle(bool useScript) {
|
||||
@@ -368,18 +369,18 @@ void SkyBrowserModule::disableHoverCircle(bool useScript) {
|
||||
void SkyBrowserModule::loadImages(const std::string& root,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
_dataHandler->loadImages(root, directory);
|
||||
_dataHandler.loadImages(root, directory);
|
||||
}
|
||||
|
||||
int SkyBrowserModule::nLoadedImages() const {
|
||||
return _dataHandler->nLoadedImages();
|
||||
return _dataHandler.nLoadedImages();
|
||||
}
|
||||
|
||||
const std::unique_ptr<WwtDataHandler>& SkyBrowserModule::getWwtDataHandler() const {
|
||||
const WwtDataHandler& SkyBrowserModule::wwtDataHandler() const {
|
||||
return _dataHandler;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& SkyBrowserModule::getPairs() {
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& SkyBrowserModule::pairs() {
|
||||
return _targetsBrowsers;
|
||||
}
|
||||
|
||||
@@ -387,7 +388,7 @@ int SkyBrowserModule::nPairs() const {
|
||||
return static_cast<int>(_targetsBrowsers.size());
|
||||
}
|
||||
|
||||
TargetBrowserPair* SkyBrowserModule::pair(const std::string& id) const {
|
||||
TargetBrowserPair* SkyBrowserModule::pair(std::string_view id) const {
|
||||
auto it = std::find_if(
|
||||
_targetsBrowsers.begin(),
|
||||
_targetsBrowsers.end(),
|
||||
@@ -416,7 +417,7 @@ void SkyBrowserModule::startRotatingCamera(glm::dvec3 endAnimation) {
|
||||
|
||||
void SkyBrowserModule::incrementallyRotateCamera() {
|
||||
if (_cameraRotation.isAnimating()) {
|
||||
glm::dmat4 rotMat = _cameraRotation.getRotationMatrix();
|
||||
glm::dmat4 rotMat = _cameraRotation.rotationMatrix();
|
||||
global::navigationHandler->camera()->rotate(glm::quat_cast(rotMat));
|
||||
}
|
||||
}
|
||||
@@ -445,9 +446,9 @@ std::string SkyBrowserModule::wwtImageCollectionUrl() const {
|
||||
return _wwtImageCollectionUrl;
|
||||
}
|
||||
|
||||
void SkyBrowserModule::setSelectedBrowser(const std::string& id) {
|
||||
TargetBrowserPair* found = pair(id);
|
||||
if (found) {
|
||||
void SkyBrowserModule::setSelectedBrowser(std::string_view id) {
|
||||
TargetBrowserPair* p = pair(id);
|
||||
if (p) {
|
||||
_selectedBrowser = id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,24 +25,21 @@
|
||||
#ifndef __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
|
||||
#include <openspace/util/openspacemodule.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/util/distanceconstants.h>
|
||||
#include <openspace/util/mouse.h>
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <modules/skybrowser/include/wwtdatahandler.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class ScreenSpaceImageLocal;
|
||||
class WwtDataHandler;
|
||||
class TargetBrowserPair;
|
||||
class SceneGraphNode;
|
||||
struct ImageData;
|
||||
class SceneGraphNode;
|
||||
class ScreenSpaceImageLocal;
|
||||
class TargetBrowserPair;
|
||||
|
||||
class SkyBrowserModule : public OpenSpaceModule {
|
||||
public:
|
||||
@@ -50,25 +47,24 @@ public:
|
||||
|
||||
SkyBrowserModule();
|
||||
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& getPairs();
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& pairs();
|
||||
int nPairs() const;
|
||||
TargetBrowserPair* pair(const std::string& id) const;
|
||||
const std::unique_ptr<WwtDataHandler>& getWwtDataHandler() const;
|
||||
TargetBrowserPair* pair(std::string_view id) const;
|
||||
const WwtDataHandler& wwtDataHandler() const;
|
||||
std::string selectedBrowserId() const;
|
||||
std::string selectedTargetId() const;
|
||||
int uniqueIdentifierCounter() const;
|
||||
|
||||
void setSelectedBrowser(const std::string& id);
|
||||
void setSelectedBrowser(std::string_view id);
|
||||
void setHoverCircle(SceneGraphNode* circle);
|
||||
|
||||
// Rotation, animation, placement
|
||||
void lookAtTarget(const std::string& id);
|
||||
void startRotatingCamera(glm::dvec3 endAnimation); // Pass in galactic coordinate
|
||||
void incrementallyRotateCamera();
|
||||
void incrementallyAnimateTargets();
|
||||
double targetAnimationSpeed() const;
|
||||
double browserAnimationSpeed() const;
|
||||
double spaceCraftAnimationTime() const;
|
||||
|
||||
std::string wwtImageCollectionUrl() const;
|
||||
|
||||
bool isCameraInSolarSystem() const;
|
||||
@@ -94,6 +90,9 @@ protected:
|
||||
void internalInitialize(const ghoul::Dictionary& dict) override;
|
||||
|
||||
private:
|
||||
void incrementallyRotateCamera();
|
||||
void incrementallyAnimateTargets();
|
||||
|
||||
properties::BoolProperty _enabled;
|
||||
properties::BoolProperty _showTitleInGuiBrowser;
|
||||
properties::BoolProperty _allowCameraRotation;
|
||||
@@ -108,7 +107,7 @@ private:
|
||||
// The browsers and targets
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>> _targetsBrowsers;
|
||||
SceneGraphNode* _hoverCircle = nullptr;
|
||||
std::string _selectedBrowser = ""; // Currently selected browser
|
||||
std::string _selectedBrowser; // Currently selected browser
|
||||
int _uniqueIdentifierCounter = 0;
|
||||
|
||||
// Flags
|
||||
@@ -119,7 +118,7 @@ private:
|
||||
skybrowser::Animation(glm::dvec3(0.0), glm::dvec3(0.0), 0.0);
|
||||
|
||||
// Data handler for the image collections
|
||||
std::unique_ptr<WwtDataHandler> _dataHandler;
|
||||
WwtDataHandler _dataHandler;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace {
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
TargetBrowserPair* selected = module->pair(module->selectedBrowserId());
|
||||
if (selected) {
|
||||
const ImageData& image = module->getWwtDataHandler()->getImage(imageIndex);
|
||||
const ImageData& image = module->wwtDataHandler().image(imageIndex);
|
||||
// Load image into browser
|
||||
std::string str = image.name;
|
||||
// Check if character is ASCII - if it isn't, remove
|
||||
@@ -147,7 +147,7 @@ namespace {
|
||||
TargetBrowserPair* pair = module->pair(identifier);
|
||||
if (pair) {
|
||||
pair->hideChromeInterface();
|
||||
pair->loadImageCollection(module->wwtImageCollectionUrl());
|
||||
pair->browser()->loadImageCollection(module->wwtImageCollectionUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace {
|
||||
// Set all border colors to the border color in the master node
|
||||
if (global::windowDelegate->isMaster()) {
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
glm::ivec3 color = pair->borderColor();
|
||||
@@ -194,7 +194,7 @@ namespace {
|
||||
// This is called when the sky_browser website is connected to OpenSpace
|
||||
// Send out identifiers to the browsers
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
pair->sendIdToBrowser();
|
||||
}
|
||||
@@ -257,9 +257,9 @@ namespace {
|
||||
// Send image list to GUI
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
std::string url = module->wwtImageCollectionUrl();
|
||||
// If no data has been loaded yet, download the data from the web!
|
||||
// If no data has been loaded yet, download the data from the web
|
||||
if (module->nLoadedImages() == 0) {
|
||||
std::filesystem::path directory = absPath("${MODULE_SKYBROWSER}/wwtimagedata/");
|
||||
std::filesystem::path directory = absPath("${SYNC}/wwtimagedata/");
|
||||
module->loadImages(url, directory);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace {
|
||||
ghoul::Dictionary list;
|
||||
|
||||
for (int i = 0; i < module->nLoadedImages(); i++) {
|
||||
const ImageData& img = module->getWwtDataHandler()->getImage(i);
|
||||
const ImageData& img = module->wwtDataHandler().image(i);
|
||||
|
||||
// Push ("Key", value)
|
||||
ghoul::Dictionary image;
|
||||
@@ -327,7 +327,7 @@ namespace {
|
||||
|
||||
// Pass data for all the browsers and the corresponding targets
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
@@ -562,7 +562,7 @@ namespace {
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
TargetBrowserPair* pair = module->pair(identifier);
|
||||
if (pair) {
|
||||
pair->removeSelectedImage(imageIndex);
|
||||
pair->browser()->removeSelectedImage(imageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +740,7 @@ namespace {
|
||||
std::for_each(
|
||||
images.rbegin(), images.rend(),
|
||||
[&](int index) {
|
||||
const ImageData& image = module->getWwtDataHandler()->getImage(index);
|
||||
const ImageData& image = module->wwtDataHandler().image(index);
|
||||
// Index of image is used as layer ID as it is unique in the image data set
|
||||
pair->browser()->addImageLayerToWwt(image.imageUrl, index);
|
||||
}
|
||||
@@ -755,7 +755,7 @@ namespace {
|
||||
[[codegen::luawrap]] void showAllTargetsAndBrowsers(bool show) {
|
||||
using namespace openspace;
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
pair->setEnabled(show);
|
||||
}
|
||||
|
||||
@@ -55,9 +55,6 @@ namespace {
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(Browser)]] Parameters {
|
||||
// [[codegen::verbatim(DimensionsInfo.description)]]
|
||||
std::optional<glm::vec2> dimensions;
|
||||
|
||||
// [[codegen::verbatim(UrlInfo.description)]]
|
||||
std::optional<std::string> url;
|
||||
|
||||
@@ -66,7 +63,6 @@ namespace {
|
||||
};
|
||||
|
||||
#include "browser_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
@@ -80,29 +76,21 @@ void Browser::RenderHandler::setTexture(GLuint t) {
|
||||
}
|
||||
|
||||
Browser::Browser(const ghoul::Dictionary& dictionary)
|
||||
: _browserPixeldimensions(
|
||||
: _browserDimensions(
|
||||
DimensionsInfo,
|
||||
glm::vec2(500.f),
|
||||
global::windowDelegate->currentSubwindowSize(),
|
||||
glm::vec2(10.f),
|
||||
glm::vec2(3000.f)
|
||||
)
|
||||
, _url(UrlInfo)
|
||||
, _reload(ReloadInfo)
|
||||
{
|
||||
if (dictionary.hasValue<std::string>(UrlInfo.identifier)) {
|
||||
_url = dictionary.value<std::string>(UrlInfo.identifier);
|
||||
}
|
||||
|
||||
// Handle target dimension property
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
_url = p.url.value_or(_url);
|
||||
_browserPixeldimensions = p.dimensions.value_or(_browserPixeldimensions);
|
||||
|
||||
glm::vec2 windowDimensions = global::windowDelegate->currentSubwindowSize();
|
||||
_browserPixeldimensions = windowDimensions;
|
||||
|
||||
_url.onChange([this]() { _isUrlDirty = true; });
|
||||
_browserPixeldimensions.onChange([this]() { _isDimensionsDirty = true; });
|
||||
|
||||
_browserDimensions.onChange([this]() { _isDimensionsDirty = true; });
|
||||
_reload.onChange([this]() { _shouldReload = true; });
|
||||
|
||||
// Create browser and render handler
|
||||
@@ -121,9 +109,9 @@ Browser::Browser(const ghoul::Dictionary& dictionary)
|
||||
|
||||
Browser::~Browser() {}
|
||||
|
||||
bool Browser::initializeGL() {
|
||||
void Browser::initializeGL() {
|
||||
_texture = std::make_unique<ghoul::opengl::Texture>(
|
||||
glm::uvec3(glm::ivec2(_browserPixeldimensions.value()), 1),
|
||||
glm::uvec3(glm::ivec2(_browserDimensions.value()), 1),
|
||||
GL_TEXTURE_2D
|
||||
);
|
||||
|
||||
@@ -131,7 +119,6 @@ bool Browser::initializeGL() {
|
||||
|
||||
_browserInstance->initialize();
|
||||
_browserInstance->loadUrl(_url);
|
||||
return isReady();
|
||||
}
|
||||
|
||||
void Browser::deinitializeGL() {
|
||||
@@ -164,11 +151,11 @@ void Browser::update() {
|
||||
_browserInstance->loadUrl(_url);
|
||||
_isUrlDirty = false;
|
||||
}
|
||||
|
||||
if (_isDimensionsDirty) {
|
||||
if (_browserPixeldimensions.value().x > 0 &&
|
||||
_browserPixeldimensions.value().y > 0)
|
||||
{
|
||||
_browserInstance->reshape(_browserPixeldimensions.value());
|
||||
glm::vec2 dim = _browserDimensions;
|
||||
if (dim.x > 0 && dim.y > 0) {
|
||||
_browserInstance->reshape(dim);
|
||||
_isDimensionsDirty = false;
|
||||
}
|
||||
}
|
||||
@@ -184,12 +171,12 @@ bool Browser::isReady() const {
|
||||
}
|
||||
|
||||
glm::vec2 Browser::browserPixelDimensions() const {
|
||||
return _browserPixeldimensions;
|
||||
return _browserDimensions;
|
||||
}
|
||||
|
||||
// Updates the browser size to match the size of the texture
|
||||
void Browser::updateBrowserSize() {
|
||||
_browserPixeldimensions = _texture->dimensions();
|
||||
_browserDimensions = _texture->dimensions();
|
||||
}
|
||||
|
||||
float Browser::browserRatio() const {
|
||||
@@ -198,23 +185,19 @@ float Browser::browserRatio() const {
|
||||
}
|
||||
|
||||
void Browser::setCallbackDimensions(const std::function<void(const glm::dvec2&)>& func) {
|
||||
_browserPixeldimensions.onChange([&]() {
|
||||
func(_browserPixeldimensions.value());
|
||||
_browserDimensions.onChange([&]() {
|
||||
func(_browserDimensions.value());
|
||||
});
|
||||
}
|
||||
|
||||
void Browser::executeJavascript(const std::string& script) const {
|
||||
// Make sure that the browser has a main frame
|
||||
const bool browserExists = _browserInstance && _browserInstance->getBrowser();
|
||||
const bool frameIsLoaded = browserExists &&
|
||||
_browserInstance->getBrowser()->GetMainFrame();
|
||||
bool browserExists = _browserInstance && _browserInstance->getBrowser();
|
||||
bool frameIsLoaded = browserExists && _browserInstance->getBrowser()->GetMainFrame();
|
||||
|
||||
if (frameIsLoaded) {
|
||||
_browserInstance->getBrowser()->GetMainFrame()->ExecuteJavaScript(
|
||||
script,
|
||||
_browserInstance->getBrowser()->GetMainFrame()->GetURL(),
|
||||
0
|
||||
);
|
||||
CefRefPtr<CefFrame> frame = _browserInstance->getBrowser()->GetMainFrame();
|
||||
frame->ExecuteJavaScript(script, frame->GetURL(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ ScreenSpaceSkyBrowser::ScreenSpaceSkyBrowser(const ghoul::Dictionary& dictionary
|
||||
|
||||
addProperty(_isHidden);
|
||||
addProperty(_url);
|
||||
addProperty(_browserPixeldimensions);
|
||||
addProperty(_browserDimensions);
|
||||
addProperty(_reload);
|
||||
addProperty(_textureQuality);
|
||||
|
||||
@@ -198,7 +198,7 @@ void ScreenSpaceSkyBrowser::updateTextureResolution() {
|
||||
float newResX = newResY * _ratio;
|
||||
glm::vec2 newSize = glm::vec2(newResX , newResY) * _textureQuality.value();
|
||||
|
||||
_browserPixeldimensions = glm::ivec2(newSize);
|
||||
_browserDimensions = glm::ivec2(newSize);
|
||||
_texture->setDimensions(glm::ivec3(newSize, 1));
|
||||
_objectSize = glm::ivec3(_texture->dimensions());
|
||||
_radiusIsDirty = true;
|
||||
|
||||
@@ -40,6 +40,22 @@
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
void aimTargetGalactic(std::string id, glm::dvec3 direction) {
|
||||
glm::dvec3 positionCelestial = glm::normalize(direction) *
|
||||
openspace::skybrowser::CelestialSphereRadius;
|
||||
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(positionCelestial)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
openspace::scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode,
|
||||
@@ -53,31 +69,10 @@ TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode,
|
||||
_targetRenderable = dynamic_cast<RenderableSkyTarget*>(_targetNode->renderable());
|
||||
}
|
||||
|
||||
TargetBrowserPair& TargetBrowserPair::operator=(TargetBrowserPair other) {
|
||||
std::swap(_targetNode, other._targetNode);
|
||||
std::swap(_browser, other._browser);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setImageOrder(int i, int order) {
|
||||
_browser->setImageOrder(i, order);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::aimTargetGalactic(glm::dvec3 direction) {
|
||||
std::string id = _targetNode->identifier();
|
||||
glm::dvec3 positionCelestial = glm::normalize(direction) *
|
||||
skybrowser::CelestialSphereRadius;
|
||||
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(positionCelestial)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::startFinetuningTarget() {
|
||||
_startTargetPosition = _targetNode->worldPosition();
|
||||
}
|
||||
@@ -100,7 +95,7 @@ void TargetBrowserPair::fineTuneTarget(const glm::vec2& startMouse,
|
||||
);
|
||||
|
||||
glm::dvec3 translationWorld = endWorld - startWorld;
|
||||
aimTargetGalactic(_startTargetPosition + translationWorld);
|
||||
aimTargetGalactic(_targetNode->identifier(), _startTargetPosition + translationWorld);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::synchronizeAim() {
|
||||
@@ -116,11 +111,6 @@ void TargetBrowserPair::setEnabled(bool enable) {
|
||||
_targetRenderable->property("Enabled")->set(enable);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setOpacity(float opacity) {
|
||||
_browser->property("Opacity")->set(opacity);
|
||||
_targetRenderable->property("Opacity")->set(opacity);
|
||||
}
|
||||
|
||||
bool TargetBrowserPair::isEnabled() const {
|
||||
return _targetRenderable->isEnabled() || _browser->isEnabled();
|
||||
}
|
||||
@@ -165,10 +155,6 @@ std::string TargetBrowserPair::targetNodeId() const {
|
||||
return _targetNode->identifier();
|
||||
}
|
||||
|
||||
float TargetBrowserPair::browserRatio() const {
|
||||
return _browser->browserRatio();
|
||||
}
|
||||
|
||||
double TargetBrowserPair::verticalFov() const {
|
||||
return _browser->verticalFov();
|
||||
}
|
||||
@@ -190,7 +176,7 @@ ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const {
|
||||
res.setValue("roll", targetRoll());
|
||||
res.setValue("color", borderColor());
|
||||
res.setValue("cartesianDirection", cartesian);
|
||||
res.setValue("ratio", static_cast<double>(browserRatio()));
|
||||
res.setValue("ratio", static_cast<double>(_browser->browserRatio()));
|
||||
res.setValue("isFacingCamera", isFacingCamera());
|
||||
res.setValue("isUsingRae", isUsingRadiusAzimuthElevation());
|
||||
res.setValue("selectedImages", selectedImages());
|
||||
@@ -249,19 +235,10 @@ void TargetBrowserPair::hideChromeInterface() {
|
||||
void TargetBrowserPair::sendIdToBrowser() const {
|
||||
_browser->setIdInBrowser();
|
||||
}
|
||||
|
||||
void TargetBrowserPair::updateBrowserSize() {
|
||||
_browser->updateBrowserSize();
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, glm::dvec3>> TargetBrowserPair::displayCopies() const {
|
||||
return _browser->displayCopies();
|
||||
}
|
||||
|
||||
bool TargetBrowserPair::isImageCollectionLoaded() {
|
||||
return _browser->isImageCollectionLoaded();
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setVerticalFov(double vfov) {
|
||||
_browser->setVerticalFov(vfov);
|
||||
_targetRenderable->setVerticalFov(vfov);
|
||||
@@ -269,6 +246,7 @@ void TargetBrowserPair::setVerticalFov(double vfov) {
|
||||
|
||||
void TargetBrowserPair::setEquatorialAim(const glm::dvec2& aim) {
|
||||
aimTargetGalactic(
|
||||
_targetNode->identifier(),
|
||||
skybrowser::equatorialToGalactic(skybrowser::sphericalToCartesian(aim))
|
||||
);
|
||||
_browser->setEquatorialAim(aim);
|
||||
@@ -300,16 +278,16 @@ void TargetBrowserPair::setImageCollectionIsLoaded(bool isLoaded) {
|
||||
void TargetBrowserPair::incrementallyAnimateToCoordinate() {
|
||||
// Animate the target before the field of view starts to animate
|
||||
if (_targetAnimation.isAnimating()) {
|
||||
aimTargetGalactic(_targetAnimation.getNewValue());
|
||||
aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue());
|
||||
}
|
||||
else if (!_targetAnimation.isAnimating() && _targetIsAnimating) {
|
||||
// Set the finished position
|
||||
aimTargetGalactic(_targetAnimation.getNewValue());
|
||||
aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue());
|
||||
_fovAnimation.start();
|
||||
_targetIsAnimating = false;
|
||||
}
|
||||
if (_fovAnimation.isAnimating()) {
|
||||
_browser->setVerticalFov(_fovAnimation.getNewValue());
|
||||
_browser->setVerticalFov(_fovAnimation.newValue());
|
||||
_targetRenderable->setVerticalFov(_browser->verticalFov());
|
||||
}
|
||||
}
|
||||
@@ -383,20 +361,8 @@ bool TargetBrowserPair::isUsingRadiusAzimuthElevation() const {
|
||||
return _browser->isUsingRaeCoords();
|
||||
}
|
||||
|
||||
SceneGraphNode* TargetBrowserPair::targetNode() const {
|
||||
return _targetNode;
|
||||
}
|
||||
|
||||
ScreenSpaceSkyBrowser* TargetBrowserPair::browser() const {
|
||||
return _browser;
|
||||
}
|
||||
|
||||
bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) {
|
||||
return lhs._targetNode == rhs._targetNode && lhs._browser == rhs._browser;
|
||||
}
|
||||
|
||||
bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -32,6 +32,20 @@
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
// Galactic coordinates are projected onto the celestial sphere
|
||||
// Equatorial coordinates are unit length
|
||||
// Conversion spherical <-> Cartesian
|
||||
|
||||
// Conversion matrix - J2000 equatorial <-> galactic
|
||||
// https://arxiv.org/abs/1010.3773v1
|
||||
constexpr glm::dmat3 ConversionMatrix = glm::dmat3(
|
||||
-0.054875539390, 0.494109453633, -0.867666135681, // col 0
|
||||
-0.873437104725, -0.444829594298, -0.198076389622, // col 1
|
||||
-0.483834991775, 0.746982248696, 0.455983794523 // col 2
|
||||
);
|
||||
} // namespace
|
||||
|
||||
namespace openspace::skybrowser {
|
||||
|
||||
// Converts from spherical coordinates in the unit of degrees to cartesian coordianates
|
||||
@@ -53,26 +67,26 @@ glm::dvec2 cartesianToSpherical(const glm::dvec3& coord) {
|
||||
double ra = atan2(coord.y, coord.x);
|
||||
double dec = atan2(coord.z, glm::sqrt((coord.x * coord.x) + (coord.y * coord.y)));
|
||||
|
||||
ra = ra > 0 ? ra : ra + glm::two_pi<double>();
|
||||
ra = ra > 0.0 ? ra : ra + glm::two_pi<double>();
|
||||
|
||||
glm::dvec2 celestialCoords = glm::dvec2(ra, dec);
|
||||
return glm::degrees(celestialCoords);
|
||||
}
|
||||
|
||||
glm::dvec3 galacticToEquatorial(const glm::dvec3& coords) {
|
||||
return glm::transpose(conversionMatrix) * glm::normalize(coords);
|
||||
return glm::transpose(ConversionMatrix) * glm::normalize(coords);
|
||||
}
|
||||
|
||||
glm::dvec3 equatorialToGalactic(const glm::dvec3& coords) {
|
||||
// On the unit sphere
|
||||
glm::dvec3 rGalactic = conversionMatrix * glm::normalize(coords);
|
||||
glm::dvec3 rGalactic = ConversionMatrix * glm::normalize(coords);
|
||||
return rGalactic;
|
||||
}
|
||||
|
||||
glm::dvec3 localCameraToScreenSpace3d(const glm::dvec3& coords) {
|
||||
// Ensure that if the coord is behind the camera,
|
||||
// the converted coordinate will be there too
|
||||
double zCoord = coords.z > 0 ? -ScreenSpaceZ : ScreenSpaceZ;
|
||||
double zCoord = coords.z > 0.0 ? -ScreenSpaceZ : ScreenSpaceZ;
|
||||
|
||||
// Calculate screen space coords x and y
|
||||
double tanX = coords.x / coords.z;
|
||||
@@ -91,15 +105,15 @@ glm::dvec3 localCameraToGalactic(const glm::dvec3& coords) {
|
||||
// Subtract camera position to get the view direction
|
||||
glm::dvec3 galactic = glm::dvec3(camMat * coordsVec4) - camPos;
|
||||
|
||||
return glm::normalize(galactic) * skybrowser::CelestialSphereRadius;
|
||||
return glm::normalize(galactic) * CelestialSphereRadius;
|
||||
}
|
||||
|
||||
glm::dvec3 localCameraToEquatorial(const glm::dvec3& coords) {
|
||||
// Calculate the galactic coordinate of the target direction
|
||||
// projected onto the celestial sphere
|
||||
glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3();
|
||||
glm::dvec3 galactic = camPos + skybrowser::localCameraToGalactic(coords);
|
||||
return skybrowser::galacticToEquatorial(galactic);
|
||||
glm::dvec3 galactic = camPos + localCameraToGalactic(coords);
|
||||
return galacticToEquatorial(galactic);
|
||||
}
|
||||
|
||||
glm::dvec3 equatorialToLocalCamera(const glm::dvec3& coords) {
|
||||
@@ -117,8 +131,10 @@ glm::dvec3 galacticToLocalCamera(const glm::dvec3& coords) {
|
||||
}
|
||||
|
||||
double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) {
|
||||
glm::dvec3 upJ2000 = skybrowser::galacticToEquatorial(up);
|
||||
glm::dvec3 forwardJ2000 = skybrowser::galacticToEquatorial(forward);
|
||||
constexpr glm::dvec3 NorthPole = glm::dvec3(0.0, 0.0, 1.0);
|
||||
|
||||
glm::dvec3 upJ2000 = galacticToEquatorial(up);
|
||||
glm::dvec3 forwardJ2000 = galacticToEquatorial(forward);
|
||||
|
||||
glm::dvec3 crossUpNorth = glm::cross(upJ2000, NorthPole);
|
||||
double dotNorthUp = glm::dot(NorthPole, upJ2000);
|
||||
@@ -129,14 +145,15 @@ double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) {
|
||||
|
||||
glm::dvec3 cameraDirectionEquatorial() {
|
||||
// Get the view direction of the screen in cartesian J2000 coordinates
|
||||
return galacticToEquatorial(cameraDirectionGalactic());
|
||||
glm::dvec3 camDirGalactic = cameraDirectionGalactic();
|
||||
return galacticToEquatorial(camDirGalactic);
|
||||
}
|
||||
|
||||
glm::dvec3 cameraDirectionGalactic() {
|
||||
// Get the view direction of the screen in galactic coordinates
|
||||
glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3();
|
||||
glm::dvec3 view = global::navigationHandler->camera()->viewDirectionWorldSpace();
|
||||
glm::dvec3 galCoord = camPos + (skybrowser::CelestialSphereRadius * view);
|
||||
glm::dvec3 galCoord = camPos + CelestialSphereRadius * view;
|
||||
|
||||
return galCoord;
|
||||
}
|
||||
@@ -150,11 +167,10 @@ bool isCoordinateInView(const glm::dvec3& equatorial) {
|
||||
// Check if image coordinate is within current FOV
|
||||
glm::dvec3 localCamera = equatorialToLocalCamera(equatorial);
|
||||
glm::dvec3 coordsScreen = localCameraToScreenSpace3d(localCamera);
|
||||
double r = static_cast<float>(windowRatio());
|
||||
|
||||
bool isCoordInView = abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f &&
|
||||
coordsScreen.z < 0;
|
||||
double r = windowRatio();
|
||||
|
||||
bool isCoordInView =
|
||||
abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f && coordsScreen.z < 0.f;
|
||||
return isCoordInView;
|
||||
}
|
||||
|
||||
@@ -200,31 +216,18 @@ glm::dmat4 incrementalAnimationMatrix(const glm::dvec3& start, const glm::dvec3&
|
||||
}
|
||||
|
||||
double sizeFromFov(double fov, glm::dvec3 worldPosition) {
|
||||
|
||||
// Calculate the size with trigonometry
|
||||
// /|
|
||||
// /_| Adjacent is the horizontal line, opposite the vertical
|
||||
// \ | Calculate for half the triangle first, then multiply with 2
|
||||
// \|
|
||||
double adjacent = glm::length(worldPosition);
|
||||
double opposite = 2 * adjacent * glm::tan(glm::radians(fov * 0.5));
|
||||
double opposite = 2.0 * adjacent * glm::tan(glm::radians(fov * 0.5));
|
||||
return opposite;
|
||||
}
|
||||
|
||||
template <>
|
||||
float Animation<float>::getNewValue() {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
else {
|
||||
float percentage = static_cast<float>(percentageSpent());
|
||||
float diff = static_cast<float>((_goal - _start) * ghoul::exponentialEaseOut(percentage));
|
||||
return _start + diff;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
double Animation<double>::getNewValue() {
|
||||
double Animation<double>::newValue() const {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
@@ -236,7 +239,7 @@ double Animation<double>::getNewValue() {
|
||||
}
|
||||
|
||||
template <>
|
||||
glm::dmat4 Animation<glm::dvec3>::getRotationMatrix() {
|
||||
glm::dmat4 Animation<glm::dvec3>::rotationMatrix() {
|
||||
if (!isAnimating()) {
|
||||
return glm::dmat4(1.0);
|
||||
}
|
||||
@@ -254,7 +257,7 @@ glm::dmat4 Animation<glm::dvec3>::getRotationMatrix() {
|
||||
}
|
||||
|
||||
template <>
|
||||
glm::dvec3 Animation<glm::dvec3>::getNewValue() {
|
||||
glm::dvec3 Animation<glm::dvec3>::newValue() const {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,90 @@
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "WwtCommunicator";
|
||||
|
||||
// WWT messages
|
||||
ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov,
|
||||
double roll)
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "center_on_coordinates"s);
|
||||
msg.setValue("ra", celestCoords.x);
|
||||
msg.setValue("dec", celestCoords.y);
|
||||
msg.setValue("fov", fov);
|
||||
msg.setValue("roll", roll);
|
||||
msg.setValue("instant", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary loadCollectionMessage(const std::string& url) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "load_image_collection"s);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("loadChildFolders", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setForegroundMessage(const std::string& name) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "set_foreground_by_name"s);
|
||||
msg.setValue("name", name);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary addImageMessage(const std::string& id, const std::string& url) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_create"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("mode", "preloaded"s);
|
||||
msg.setValue("goto", false);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary removeImageMessage(const std::string& imageId) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_remove"s);
|
||||
msg.setValue("id", imageId);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setImageOpacityMessage(const std::string& imageId, double opacity) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_set"s);
|
||||
msg.setValue("id", imageId);
|
||||
msg.setValue("setting", "opacity"s);
|
||||
msg.setValue("value", opacity);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setLayerOrderMessage(const std::string& id, int order) {
|
||||
static int MessageCounter = 0;
|
||||
|
||||
// The lower the layer order, the more towards the back the image is placed
|
||||
// 0 is the background
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_order"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("order", order);
|
||||
msg.setValue("version", MessageCounter);
|
||||
|
||||
MessageCounter++;
|
||||
return msg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
@@ -40,7 +124,27 @@ WwtCommunicator::WwtCommunicator(const ghoul::Dictionary& dictionary)
|
||||
: Browser(dictionary)
|
||||
{}
|
||||
|
||||
WwtCommunicator::~WwtCommunicator() {}
|
||||
void WwtCommunicator::update() {
|
||||
// Cap how messages are passed
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime;
|
||||
|
||||
if (timeSinceLastUpdate > TimeUpdateInterval) {
|
||||
if (_equatorialAimIsDirty) {
|
||||
updateAim();
|
||||
_equatorialAimIsDirty = false;
|
||||
}
|
||||
if (_borderColorIsDirty) {
|
||||
updateBorderColor();
|
||||
_borderColorIsDirty = false;
|
||||
}
|
||||
_lastUpdateTime = std::chrono::system_clock::now();
|
||||
}
|
||||
if (_shouldReload) {
|
||||
_isImageCollectionLoaded = false;
|
||||
}
|
||||
Browser::update();
|
||||
}
|
||||
|
||||
void WwtCommunicator::selectImage(const std::string& url, int i) {
|
||||
// Ensure there are no duplicates
|
||||
@@ -66,7 +170,6 @@ void WwtCommunicator::addImageLayerToWwt(const std::string& url, int i) {
|
||||
void WwtCommunicator::removeSelectedImage(int i) {
|
||||
// Remove from selected list
|
||||
auto it = findSelectedImage(i);
|
||||
|
||||
if (it != _selectedImages.end()) {
|
||||
_selectedImages.erase(it);
|
||||
sendMessageToWwt(removeImageMessage(std::to_string(i)));
|
||||
@@ -74,23 +177,31 @@ void WwtCommunicator::removeSelectedImage(int i) {
|
||||
}
|
||||
|
||||
void WwtCommunicator::sendMessageToWwt(const ghoul::Dictionary& msg) const {
|
||||
std::string script = "sendMessageToWWT(" + ghoul::formatJson(msg) + ");";
|
||||
executeJavascript(script);
|
||||
std::string m = ghoul::formatJson(msg);
|
||||
executeJavascript(fmt::format("sendMessageToWWT({});", m));
|
||||
}
|
||||
|
||||
std::vector<int> WwtCommunicator::selectedImages() const {
|
||||
std::vector<int> selectedImagesVector;
|
||||
for (const std::pair<int, double>& image : _selectedImages) {
|
||||
selectedImagesVector.push_back(image.first);
|
||||
}
|
||||
selectedImagesVector.resize(_selectedImages.size());
|
||||
std::transform(
|
||||
_selectedImages.cbegin(),
|
||||
_selectedImages.cend(),
|
||||
selectedImagesVector.begin(),
|
||||
[](const std::pair<int, double>& image) { return image.first; }
|
||||
);
|
||||
return selectedImagesVector;
|
||||
}
|
||||
|
||||
std::vector<double> WwtCommunicator::opacities() const {
|
||||
std::vector<double> opacities;
|
||||
for (const std::pair<int, double>& image : _selectedImages) {
|
||||
opacities.push_back(image.second);
|
||||
}
|
||||
opacities.resize(_selectedImages.size());
|
||||
std::transform(
|
||||
_selectedImages.cbegin(),
|
||||
_selectedImages.cend(),
|
||||
opacities.begin(),
|
||||
[](const std::pair<int, double>& image) { return image.second; }
|
||||
);
|
||||
return opacities;
|
||||
}
|
||||
|
||||
@@ -107,12 +218,6 @@ void WwtCommunicator::setVerticalFov(double vfov) {
|
||||
_equatorialAimIsDirty = true;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setWebpageBorderColor(glm::ivec3 color) const {
|
||||
std::string stringColor = fmt::format("{},{},{}", color.x, color.y, color.z);
|
||||
std::string scr = "setBackgroundColor('rgb(" + stringColor + ")');";
|
||||
executeJavascript(scr);
|
||||
}
|
||||
|
||||
void WwtCommunicator::setEquatorialAim(glm::dvec2 equatorial) {
|
||||
_equatorialAim = std::move(equatorial);
|
||||
_equatorialAimIsDirty = true;
|
||||
@@ -130,7 +235,11 @@ void WwtCommunicator::setBorderRadius(double radius) {
|
||||
}
|
||||
|
||||
void WwtCommunicator::updateBorderColor() const {
|
||||
setWebpageBorderColor(_borderColor);
|
||||
std::string script = fmt::format(
|
||||
"document.body.style.backgroundColor = 'rgb({},{},{})';",
|
||||
_borderColor.x, _borderColor.y, _borderColor.z
|
||||
);
|
||||
executeJavascript(script);
|
||||
}
|
||||
|
||||
void WwtCommunicator::updateAim() const {
|
||||
@@ -140,8 +249,9 @@ void WwtCommunicator::updateAim() const {
|
||||
}
|
||||
|
||||
glm::dvec2 WwtCommunicator::fieldsOfView() const {
|
||||
glm::dvec2 browserFov = glm::dvec2(verticalFov() * browserRatio(), verticalFov());
|
||||
return browserFov;
|
||||
const double vFov = verticalFov();
|
||||
const double hFov = vFov * browserRatio();
|
||||
return glm::dvec2(hFov, vFov);
|
||||
}
|
||||
|
||||
bool WwtCommunicator::isImageCollectionLoaded() const {
|
||||
@@ -149,10 +259,11 @@ bool WwtCommunicator::isImageCollectionLoaded() const {
|
||||
}
|
||||
|
||||
std::deque<std::pair<int, double>>::iterator WwtCommunicator::findSelectedImage(int i) {
|
||||
auto it = std::find_if(_selectedImages.begin(), _selectedImages.end(),
|
||||
[i](std::pair<int, double>& pair) {
|
||||
return (pair.first == i);
|
||||
});
|
||||
auto it = std::find_if(
|
||||
_selectedImages.begin(),
|
||||
_selectedImages.end(),
|
||||
[i](const std::pair<int, double>& pair) { return pair.first == i; }
|
||||
);
|
||||
return it;
|
||||
}
|
||||
|
||||
@@ -196,35 +307,13 @@ void WwtCommunicator::hideChromeInterface() const {
|
||||
executeJavascript(script);
|
||||
}
|
||||
|
||||
void WwtCommunicator::update() {
|
||||
// Cap how messages are passed
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime;
|
||||
|
||||
if (timeSinceLastUpdate > TimeUpdateInterval) {
|
||||
if (_equatorialAimIsDirty) {
|
||||
updateAim();
|
||||
_equatorialAimIsDirty = false;
|
||||
}
|
||||
if (_borderColorIsDirty) {
|
||||
updateBorderColor();
|
||||
_borderColorIsDirty = false;
|
||||
}
|
||||
_lastUpdateTime = std::chrono::system_clock::now();
|
||||
}
|
||||
if (_shouldReload) {
|
||||
_isImageCollectionLoaded = false;
|
||||
}
|
||||
Browser::update();
|
||||
}
|
||||
|
||||
void WwtCommunicator::setImageCollectionIsLoaded(bool isLoaded) {
|
||||
_isImageCollectionLoaded = isLoaded;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setIdInBrowser(const std::string& id) const {
|
||||
// Send ID to it's browser
|
||||
executeJavascript("setId('" + id + "')");
|
||||
// Send ID to its browser
|
||||
executeJavascript(fmt::format("setId('{}')", id));
|
||||
}
|
||||
|
||||
glm::ivec3 WwtCommunicator::borderColor() const {
|
||||
@@ -235,92 +324,4 @@ double WwtCommunicator::verticalFov() const {
|
||||
return _verticalFov;
|
||||
}
|
||||
|
||||
// WWT messages
|
||||
ghoul::Dictionary WwtCommunicator::moveCameraMessage(const glm::dvec2& celestCoords,
|
||||
double fov, double roll,
|
||||
bool shouldMoveInstantly) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "center_on_coordinates"s);
|
||||
msg.setValue("ra", celestCoords.x);
|
||||
msg.setValue("dec", celestCoords.y);
|
||||
msg.setValue("fov", fov);
|
||||
msg.setValue("roll", roll);
|
||||
msg.setValue("instant", shouldMoveInstantly);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::loadCollectionMessage(const std::string& url) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "load_image_collection"s);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("loadChildFolders", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setForegroundMessage(const std::string& name) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "set_foreground_by_name"s);
|
||||
msg.setValue("name", name);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::addImageMessage(const std::string& id,
|
||||
const std::string& url) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_create"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("mode", "preloaded"s);
|
||||
msg.setValue("goto", false);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::removeImageMessage(const std::string& imageId) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_remove"s);
|
||||
msg.setValue("id", imageId);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setImageOpacityMessage(const std::string& imageId,
|
||||
double opacity) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_set"s);
|
||||
msg.setValue("id", imageId);
|
||||
msg.setValue("setting", "opacity"s);
|
||||
msg.setValue("value", opacity);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setLayerOrderMessage(const std::string& id, int order)
|
||||
{
|
||||
// The lower the layer order, the more towards the back the image is placed
|
||||
// 0 is the background
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_order"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("order", order);
|
||||
msg.setValue("version", messageCounter);
|
||||
|
||||
messageCounter++;
|
||||
return msg;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,239 +25,316 @@
|
||||
#include <modules/skybrowser/include/wwtdatahandler.h>
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/util/httprequest.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string_view>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsuggest-override"
|
||||
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
|
||||
#endif
|
||||
|
||||
#include <modules/skybrowser/ext/tinyxml2/tinyxml2.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "WwtDataHandler";
|
||||
|
||||
constexpr std::string_view Thumbnail = "Thumbnail";
|
||||
constexpr std::string_view Name = "Name";
|
||||
constexpr std::string_view ImageSet = "ImageSet";
|
||||
constexpr std::string_view Dec = "Dec";
|
||||
constexpr std::string_view RA = "RA";
|
||||
constexpr std::string_view Undefined = "";
|
||||
constexpr std::string_view Folder = "Folder";
|
||||
constexpr std::string_view Place = "Place";
|
||||
constexpr std::string_view ThumbnailUrl = "ThumbnailUrl";
|
||||
constexpr std::string_view Url = "Url";
|
||||
constexpr std::string_view Credits = "Credits";
|
||||
constexpr std::string_view CreditsUrl = "CreditsUrl";
|
||||
constexpr std::string_view ZoomLevel = "ZoomLevel";
|
||||
constexpr std::string_view DataSetType = "DataSetType";
|
||||
constexpr std::string_view Sky = "Sky";
|
||||
|
||||
bool hasAttribute(const tinyxml2::XMLElement* element, std::string_view name) {
|
||||
std::string n = std::string(name);
|
||||
return element->FindAttribute(n.c_str());
|
||||
}
|
||||
|
||||
std::string attribute(const tinyxml2::XMLElement* element, std::string_view name) {
|
||||
if (hasAttribute(element, name)) {
|
||||
std::string n = std::string(name);
|
||||
return element->FindAttribute(n.c_str())->Value();
|
||||
}
|
||||
return std::string(Undefined);
|
||||
}
|
||||
|
||||
// Parsing and downloading of wtml files
|
||||
bool downloadFile(const std::string& url, const std::filesystem::path& destination) {
|
||||
using namespace openspace;
|
||||
|
||||
HttpFileDownload wtmlRoot(url, destination, HttpFileDownload::Overwrite::Yes);
|
||||
wtmlRoot.start(std::chrono::milliseconds(10000));
|
||||
return wtmlRoot.wait();
|
||||
}
|
||||
|
||||
bool directoryExists(const std::filesystem::path& path) {
|
||||
return std::filesystem::exists(path) && std::filesystem::is_directory(path);
|
||||
}
|
||||
|
||||
const tinyxml2::XMLElement* directChildNode(const tinyxml2::XMLElement* node,
|
||||
std::string_view name)
|
||||
{
|
||||
while (node && node->Name() != name) {
|
||||
node = node->FirstChildElement();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
const tinyxml2::XMLElement* childNode(const tinyxml2::XMLElement* node,
|
||||
std::string_view name)
|
||||
{
|
||||
const tinyxml2::XMLElement* child = node->FirstChildElement();
|
||||
|
||||
// Traverse the children and look at all their first child to find ImageSet
|
||||
while (child) {
|
||||
const tinyxml2::XMLElement* imageSet = directChildNode(child, name);
|
||||
if (imageSet) {
|
||||
return imageSet;
|
||||
}
|
||||
child = child->NextSiblingElement();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string childNodeContentFromImageSet(const tinyxml2::XMLElement* imageSet,
|
||||
std::string_view elementName)
|
||||
{
|
||||
// Find the thumbnail image url
|
||||
// The thumbnail is the last node so traverse backwards for speed
|
||||
std::string n = std::string(elementName);
|
||||
const tinyxml2::XMLElement* child = imageSet->FirstChildElement(n.c_str());
|
||||
return child && child->GetText() ? child->GetText() : std::string(Undefined);
|
||||
}
|
||||
|
||||
std::string urlFromPlace(const tinyxml2::XMLElement* place) {
|
||||
// If the place has a thumbnail url, return it
|
||||
if (hasAttribute(place, Thumbnail)) {
|
||||
return attribute(place, Thumbnail);
|
||||
}
|
||||
|
||||
// If the place doesn't have a thumbnail url data attribute,
|
||||
// Load the image set it stores instead
|
||||
const tinyxml2::XMLElement* imageSet = childNode(place, ImageSet);
|
||||
|
||||
// If there is an imageSet, collect thumbnail url, if it doesn't contain an
|
||||
// ImageSet, it doesn't have an url
|
||||
return imageSet ?
|
||||
childNodeContentFromImageSet(imageSet, ThumbnailUrl) :
|
||||
std::string(Undefined);
|
||||
}
|
||||
|
||||
bool downloadWtmlFiles(const std::filesystem::path& directory, const std::string& url,
|
||||
const std::string& fileName)
|
||||
{
|
||||
using namespace openspace;
|
||||
// Download file from url
|
||||
std::filesystem::path file = directory.string() + fileName + ".aspx";
|
||||
const bool success = downloadFile(url, file);
|
||||
if (!success) {
|
||||
LINFO(fmt::format(
|
||||
"Could not download file '{}' to directory {}", url, directory
|
||||
));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse file to XML
|
||||
auto document = std::make_unique<tinyxml2::XMLDocument>();
|
||||
document->LoadFile(file.string().c_str());
|
||||
|
||||
// Search XML file for folders with urls
|
||||
const tinyxml2::XMLElement* root = document->RootElement();
|
||||
const tinyxml2::XMLElement* element = root->FirstChildElement(Folder.data());
|
||||
const bool folderExists = element != nullptr;
|
||||
const bool folderContainNoUrls = folderExists && !hasAttribute(element, Url);
|
||||
|
||||
// If the file contains no folders, or there are folders but without urls,
|
||||
// stop recursion
|
||||
if (!folderExists || folderContainNoUrls) {
|
||||
LINFO(fmt::format("Saving {}", url));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterate through all the folders in the XML file
|
||||
while (element && std::string(element->Value()) == Folder) {
|
||||
// If folder contains urls, download and parse those urls
|
||||
if (hasAttribute(element, Url) && hasAttribute(element, Name)) {
|
||||
std::string urlAttr = attribute(element, Url);
|
||||
std::string fileNameAttr = attribute(element, Name);
|
||||
downloadWtmlFiles(directory, urlAttr, fileNameAttr);
|
||||
}
|
||||
element = element->NextSiblingElement();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<openspace::ImageData> loadImageFromNode(
|
||||
const tinyxml2::XMLElement* node,
|
||||
std::string collection)
|
||||
{
|
||||
using namespace openspace;
|
||||
|
||||
// Collect the image set of the node. The structure is different depending on if
|
||||
// it is a Place or an ImageSet
|
||||
std::string thumbnailUrl = std::string(Undefined);
|
||||
const tinyxml2::XMLElement* imageSet = nullptr;
|
||||
std::string type = node->Name();
|
||||
|
||||
if (type == ImageSet) {
|
||||
thumbnailUrl = childNodeContentFromImageSet(node, ThumbnailUrl);
|
||||
imageSet = node;
|
||||
}
|
||||
else if (type == Place) {
|
||||
thumbnailUrl = urlFromPlace(node);
|
||||
imageSet = childNode(node, ImageSet);
|
||||
}
|
||||
|
||||
// Only collect the images that have a thumbnail image, that are sky images and
|
||||
// that have an image
|
||||
const bool hasThumbnailUrl = thumbnailUrl != Undefined;
|
||||
const bool isSkyImage = attribute(node, DataSetType) == Sky;
|
||||
const bool hasImageUrl = imageSet ? hasAttribute(imageSet, Url) : false;
|
||||
|
||||
if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Collect name, image url and credits
|
||||
std::string name = attribute(node, Name);
|
||||
if (std::islower(name[0])) {
|
||||
// convert string to upper case
|
||||
name[0] = static_cast<char>(std::toupper(name[0]));
|
||||
}
|
||||
|
||||
std::string imageUrl = attribute(imageSet, Url);
|
||||
std::string credits = childNodeContentFromImageSet(imageSet, Credits);
|
||||
std::string creditsUrl = childNodeContentFromImageSet(imageSet, CreditsUrl);
|
||||
|
||||
// Collect equatorial coordinates. All-sky surveys do not have these coordinates
|
||||
bool hasCelestialCoords = hasAttribute(node, RA) && hasAttribute(node, Dec);
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
glm::dvec3 equatorialCartesian = glm::dvec3(0.0);
|
||||
|
||||
if (hasCelestialCoords) {
|
||||
// The RA from WWT is in the unit hours:
|
||||
// to convert to degrees, multiply with 360 (deg) /24 (h) = 15
|
||||
double ra = 15.0 * std::stod(attribute(node, RA));
|
||||
double dec = std::stod(attribute(node, Dec));
|
||||
equatorialSpherical = glm::dvec2(ra, dec);
|
||||
equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical);
|
||||
}
|
||||
|
||||
// Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6
|
||||
float fov = 0.f;
|
||||
if (hasAttribute(node, ZoomLevel)) {
|
||||
fov = std::stof(attribute(node, ZoomLevel)) / 6.f;
|
||||
}
|
||||
|
||||
return ImageData{
|
||||
name,
|
||||
thumbnailUrl,
|
||||
imageUrl,
|
||||
credits,
|
||||
creditsUrl,
|
||||
collection,
|
||||
hasCelestialCoords,
|
||||
fov,
|
||||
equatorialSpherical,
|
||||
equatorialCartesian
|
||||
};
|
||||
}
|
||||
} //namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
bool hasAttribute(const tinyxml2::XMLElement* element, const std::string_view& name) {
|
||||
return element->FindAttribute(std::string(name).c_str());
|
||||
}
|
||||
|
||||
std::string attribute(const tinyxml2::XMLElement* element, const std::string& name) {
|
||||
if (hasAttribute(element, name)) {
|
||||
return element->FindAttribute(name.c_str())->Value();
|
||||
}
|
||||
return wwt::Undefined;
|
||||
}
|
||||
|
||||
// Parsing and downloading of wtml files
|
||||
bool downloadFile(const std::string& url, const std::filesystem::path& fileDestination) {
|
||||
// Get the web page and save to file
|
||||
HttpFileDownload wtmlRoot(
|
||||
url,
|
||||
fileDestination,
|
||||
HttpFileDownload::Overwrite::Yes
|
||||
);
|
||||
wtmlRoot.start(std::chrono::milliseconds(10000));
|
||||
return wtmlRoot.wait();
|
||||
}
|
||||
|
||||
bool directoryExists(const std::filesystem::path& path) {
|
||||
return std::filesystem::exists(path) && std::filesystem::is_directory(path);
|
||||
}
|
||||
|
||||
std::string createSearchableString(std::string str) {
|
||||
// Remove white spaces and all special characters
|
||||
str.erase(
|
||||
std::remove_if(
|
||||
str.begin(), str.end(),
|
||||
[](char c) {
|
||||
const bool isNumberOrLetter = std::isdigit(c) || std::isalpha(c);
|
||||
return !isNumberOrLetter;
|
||||
}
|
||||
),
|
||||
str.end()
|
||||
);
|
||||
// Make the word lower case
|
||||
std::transform(
|
||||
str.begin(), str.end(),
|
||||
str.begin(),
|
||||
[](char c) { return static_cast<char>(std::tolower(c)); }
|
||||
);
|
||||
return str;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* getDirectChildNode(tinyxml2::XMLElement* node,
|
||||
const std::string& name)
|
||||
{
|
||||
while (node && node->Name() != name) {
|
||||
node = node->FirstChildElement();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* getChildNode(tinyxml2::XMLElement* node,
|
||||
const std::string& name)
|
||||
{
|
||||
tinyxml2::XMLElement* child = node->FirstChildElement();
|
||||
|
||||
// Traverse the children and look at all their first child to find ImageSet
|
||||
while (child) {
|
||||
tinyxml2::XMLElement* imageSet = getDirectChildNode(child, name);
|
||||
// Found
|
||||
if (imageSet) {
|
||||
return imageSet;
|
||||
}
|
||||
child = child->NextSiblingElement();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string getChildNodeContentFromImageSet(tinyxml2::XMLElement* imageSet,
|
||||
const std::string& elementName)
|
||||
{
|
||||
// Find the thumbnail image url
|
||||
// The thumbnail is the last node so traverse backwards for speed
|
||||
tinyxml2::XMLElement* imageSetChild =
|
||||
imageSet->FirstChildElement(elementName.c_str());
|
||||
|
||||
if (imageSetChild && imageSetChild->GetText()) {
|
||||
return imageSetChild->GetText();
|
||||
}
|
||||
else {
|
||||
return wwt::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getUrlFromPlace(tinyxml2::XMLElement* place) {
|
||||
// If the place has a thumbnail url, return it
|
||||
if (hasAttribute(place, wwt::Thumbnail)) {
|
||||
return attribute(place, wwt::Thumbnail);
|
||||
}
|
||||
|
||||
// If the place doesn't have a thumbnail url data attribute,
|
||||
// Load the image set it stores instead
|
||||
tinyxml2::XMLElement* imageSet = getChildNode(place, wwt::ImageSet);
|
||||
|
||||
// If there is an imageSet, collect thumbnail url
|
||||
if (imageSet) {
|
||||
return getChildNodeContentFromImageSet(imageSet, wwt::ThumbnailUrl);
|
||||
}
|
||||
else {
|
||||
// If it doesn't contain an ImageSet, it doesn't have an url
|
||||
return wwt::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
void parseWtmlsFromDisc(std::vector<tinyxml2::XMLDocument*>& xmls,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
|
||||
std::string path = entry.path().string();
|
||||
tinyxml2::XMLError successCode = document->LoadFile(path.c_str());
|
||||
|
||||
if (successCode == tinyxml2::XMLError::XML_SUCCESS) {
|
||||
xmls.push_back(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool downloadAndParseWtmlFilesFromUrl(std::vector<tinyxml2::XMLDocument*>& xmls,
|
||||
const std::filesystem::path& directory,
|
||||
const std::string& url, const std::string& fileName)
|
||||
{
|
||||
// Look for WWT image data folder, create folder if it doesn't exist
|
||||
if (!directoryExists(directory)) {
|
||||
std::string newDir = directory.string();
|
||||
// Remove the '/' at the end
|
||||
newDir.pop_back();
|
||||
LINFO("Creating directory" + newDir);
|
||||
std::filesystem::create_directory(newDir);
|
||||
}
|
||||
|
||||
// Download file from url
|
||||
std::filesystem::path file = directory.string() + fileName + ".aspx";
|
||||
if (!downloadFile(url, file)) {
|
||||
LINFO(
|
||||
fmt::format("Couldn't download file '{}' to directory '{}'", url, directory)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse file to XML
|
||||
using namespace tinyxml2;
|
||||
tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument();
|
||||
doc->LoadFile(file.string().c_str());
|
||||
|
||||
// Search XML file for folders with urls
|
||||
XMLElement* root = doc->RootElement();
|
||||
XMLElement* element = root->FirstChildElement(wwt::Folder.c_str());
|
||||
const bool folderExists = element;
|
||||
const bool folderContainNoUrls = folderExists && !hasAttribute(element, wwt::Url);
|
||||
|
||||
// If the file contains no folders, or there are folders but without urls,
|
||||
// stop recursion
|
||||
if (!folderExists || folderContainNoUrls) {
|
||||
xmls.push_back(doc);
|
||||
LINFO("Saving " + url);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterate through all the folders in the XML file
|
||||
while (element && std::string(element->Value()) == wwt::Folder) {
|
||||
// If folder contains urls, download and parse those urls
|
||||
if (hasAttribute(element, wwt::Url) && hasAttribute(element, wwt::Name)) {
|
||||
std::string urlAttr = attribute(element, wwt::Url);
|
||||
std::string fileNameAttr = attribute(element, wwt::Name);
|
||||
downloadAndParseWtmlFilesFromUrl(xmls, directory, urlAttr, fileNameAttr);
|
||||
}
|
||||
element = element->NextSiblingElement();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WwtDataHandler::~WwtDataHandler() {
|
||||
// Call destructor of all allocated xmls
|
||||
_xmls.clear();
|
||||
}
|
||||
|
||||
void WwtDataHandler::loadImages(const std::string& root,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
// Collect the wtml files, either by reading from disc or from a url
|
||||
if (directoryExists(directory) && !std::filesystem::is_empty(directory)) {
|
||||
parseWtmlsFromDisc(_xmls, directory);
|
||||
LINFO("Loading images from directory");
|
||||
}
|
||||
else {
|
||||
downloadAndParseWtmlFilesFromUrl(_xmls, directory, root, "root");
|
||||
LINFO("Loading images from url");
|
||||
// Steps to download new images
|
||||
// 1. Create the target directory if it doesn't already exist
|
||||
// 2. If the 'root' has an associated hash file, download and compare it with the
|
||||
// local file. If the hash has changed, nuke the folder
|
||||
// 3. If the folder is empty, download files
|
||||
|
||||
// 1.
|
||||
if (!directoryExists(directory)) {
|
||||
LINFO(fmt::format("Creating directory {}", directory));
|
||||
std::filesystem::create_directory(directory);
|
||||
}
|
||||
|
||||
// Traverse through the collected wtml documents and collect the images
|
||||
for (tinyxml2::XMLDocument* doc : _xmls) {
|
||||
tinyxml2::XMLElement* rootNode = doc->FirstChildElement();
|
||||
std::string collectionName = attribute(rootNode, wwt::Name);
|
||||
saveImagesFromXml(rootNode, collectionName);
|
||||
// Get the hash from the remote. If no such hash exists, the remoteHash will be empty
|
||||
std::string remoteHash;
|
||||
{
|
||||
std::string remoteHashFile = root.substr(0, root.find_last_of('/')) + "/hash.md5";
|
||||
bool success = downloadFile(remoteHashFile, directory / "hash.tmp");
|
||||
// The hash download might fail if the provided 'root' does not have a hash
|
||||
// in which case we assume that the underlying data has not changed
|
||||
if (success) {
|
||||
std::ifstream(directory / "hash.tmp") >> remoteHash;
|
||||
std::filesystem::remove(directory / "hash.tmp");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the local hash. If no such hash exists, the localHash will be empty
|
||||
std::string localHash;
|
||||
std::filesystem::path localHashFile = directory / "hash.md5";
|
||||
if (std::filesystem::exists(localHashFile)) {
|
||||
std::ifstream(localHashFile) >> localHash;
|
||||
}
|
||||
|
||||
// Check if the hash has changed. This will be ignored if either the local of remote
|
||||
// hash does not exist
|
||||
if (!localHash.empty() && !remoteHash.empty() && localHash != remoteHash) {
|
||||
LINFO(fmt::format(
|
||||
"Local hash '{}' differs from remote hash '{}'. Cleaning directory",
|
||||
localHash, remoteHash
|
||||
));
|
||||
|
||||
std::filesystem::remove_all(directory);
|
||||
std::filesystem::create_directory(directory);
|
||||
}
|
||||
|
||||
// If there is no directory (either because it is the first start, or the previous
|
||||
// contents were deleted because of a change in hash) we have to download the files
|
||||
if (std::filesystem::is_empty(directory)) {
|
||||
LINFO("Loading images from url");
|
||||
downloadWtmlFiles(directory, root, "root");
|
||||
std::ofstream(localHashFile) << remoteHash;
|
||||
}
|
||||
|
||||
// Finally, we can load the files that are now on disk
|
||||
LINFO("Loading images from directory");
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
tinyxml2::XMLDocument document;
|
||||
std::string path = entry.path().string();
|
||||
tinyxml2::XMLError successCode = document.LoadFile(path.c_str());
|
||||
|
||||
if (successCode == tinyxml2::XMLError::XML_SUCCESS) {
|
||||
tinyxml2::XMLElement* rootNode = document.FirstChildElement();
|
||||
std::string collectionName = attribute(rootNode, Name);
|
||||
saveImagesFromXml(rootNode, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort images in alphabetical order
|
||||
std::sort(
|
||||
_images.begin(),
|
||||
_images.end(),
|
||||
[](ImageData& a, ImageData& b) {
|
||||
// If the first character in the names are lowercase, make it upper case
|
||||
if (std::islower(a.name[0])) {
|
||||
// convert string to upper case
|
||||
a.name[0] = static_cast<char>(::toupper(a.name[0]));
|
||||
}
|
||||
if (std::islower(b.name[0])) {
|
||||
b.name[0] = static_cast<char>(::toupper(b.name[0]));
|
||||
}
|
||||
return a.name < b.name;
|
||||
}
|
||||
[](ImageData& a, ImageData& b) { return a.name < b.name; }
|
||||
);
|
||||
|
||||
LINFO(fmt::format("Loaded {} WorldWide Telescope images", _images.size()));
|
||||
@@ -267,100 +344,33 @@ int WwtDataHandler::nLoadedImages() const {
|
||||
return static_cast<int>(_images.size());
|
||||
}
|
||||
|
||||
const ImageData& WwtDataHandler::getImage(int i) const {
|
||||
const ImageData& WwtDataHandler::image(int i) const {
|
||||
ghoul_assert(i < static_cast<int>(_images.size()), "Index outside of vector size");
|
||||
return _images[i];
|
||||
}
|
||||
|
||||
void WwtDataHandler::saveImageFromNode(tinyxml2::XMLElement* node, std::string collection)
|
||||
{
|
||||
// Collect the image set of the node. The structure is different depending on if
|
||||
// it is a Place or an ImageSet
|
||||
std::string thumbnailUrl = wwt::Undefined;
|
||||
tinyxml2::XMLElement* imageSet = nullptr;
|
||||
std::string type = std::string(node->Name());
|
||||
|
||||
if (type == wwt::ImageSet) {
|
||||
thumbnailUrl = getChildNodeContentFromImageSet(node, wwt::ThumbnailUrl);
|
||||
imageSet = node;
|
||||
}
|
||||
else if (type == wwt::Place) {
|
||||
thumbnailUrl = getUrlFromPlace(node);
|
||||
imageSet = getChildNode(node, wwt::ImageSet);
|
||||
}
|
||||
|
||||
// Only collect the images that have a thumbnail image, that are sky images and
|
||||
// that have an image
|
||||
const bool hasThumbnailUrl = thumbnailUrl != wwt::Undefined;
|
||||
const bool isSkyImage = attribute(node, wwt::DataSetType) == wwt::Sky;
|
||||
const bool hasImageUrl = imageSet ? hasAttribute(imageSet, wwt::Url) : false;
|
||||
|
||||
if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect name, image url and credits
|
||||
std::string name = attribute(node, wwt::Name);
|
||||
std::string imageUrl = attribute(imageSet, wwt::Url);
|
||||
std::string credits = getChildNodeContentFromImageSet(imageSet, wwt::Credits);
|
||||
std::string creditsUrl = getChildNodeContentFromImageSet(imageSet, wwt::CreditsUrl);
|
||||
|
||||
// Collect equatorial coordinates. All-sky surveys do not have this kind of
|
||||
// coordinate
|
||||
bool hasCelestialCoords = hasAttribute(node, wwt::RA) && hasAttribute(node, wwt::Dec);
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
glm::dvec3 equatorialCartesian = glm::vec3(0.0);
|
||||
|
||||
if (hasCelestialCoords) {
|
||||
// The RA from WWT is in the unit hours:
|
||||
// to convert to degrees, multiply with 360 (deg) /24 (h) = 15
|
||||
double ra = 15.0 * std::stod(attribute(node, wwt::RA));
|
||||
double dec = std::stod(attribute(node, wwt::Dec));
|
||||
equatorialSpherical = glm::dvec2(ra, dec);
|
||||
equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical);
|
||||
}
|
||||
|
||||
// Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6
|
||||
float fov = 0.f;
|
||||
if (hasAttribute(node, wwt::ZoomLevel)) {
|
||||
fov = std::stof(attribute(node, wwt::ZoomLevel)) / 6.0f;
|
||||
}
|
||||
|
||||
ImageData image = {
|
||||
name,
|
||||
thumbnailUrl,
|
||||
imageUrl,
|
||||
credits,
|
||||
creditsUrl,
|
||||
collection,
|
||||
hasCelestialCoords,
|
||||
fov,
|
||||
equatorialSpherical,
|
||||
equatorialCartesian
|
||||
};
|
||||
|
||||
_images.push_back(image);
|
||||
}
|
||||
|
||||
void WwtDataHandler::saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection)
|
||||
void WwtDataHandler::saveImagesFromXml(const tinyxml2::XMLElement* root,
|
||||
std::string collection)
|
||||
{
|
||||
// Get direct child of node called Place
|
||||
using namespace tinyxml2;
|
||||
XMLElement* node = root->FirstChildElement();
|
||||
const tinyxml2::XMLElement* node = root->FirstChildElement();
|
||||
|
||||
// Iterate through all siblings of node. If sibling is folder, open recursively.
|
||||
// If sibling is image, save it.
|
||||
while (node) {
|
||||
const std::string name = node->Name();
|
||||
// If node is an image or place, load it
|
||||
if (name == wwt::ImageSet || name == wwt::Place) {
|
||||
saveImageFromNode(node, collection);
|
||||
if (name == ImageSet || name == Place) {
|
||||
std::optional<ImageData> image = loadImageFromNode(node, collection);
|
||||
if (image.has_value()) {
|
||||
_images.push_back(std::move(*image));
|
||||
}
|
||||
|
||||
}
|
||||
// If node is another folder, open recursively
|
||||
else if (name == wwt::Folder) {
|
||||
std::string newCollectionName = collection + "/";
|
||||
newCollectionName += attribute(node, wwt::Name);
|
||||
|
||||
else if (name == Folder) {
|
||||
std::string nodeName = attribute(node, Name);
|
||||
std::string newCollectionName = fmt::format("{}/{}", collection, nodeName);
|
||||
saveImagesFromXml(node, newCollectionName);
|
||||
}
|
||||
node = node->NextSiblingElement();
|
||||
|
||||
@@ -138,7 +138,7 @@ void create() {
|
||||
#endif // WIN32
|
||||
|
||||
#ifdef WIN32
|
||||
keyboard = new (currentPos) std::vector<KeyboardCallback>
|
||||
keyboard = new (currentPos) std::vector<KeyboardCallback>();
|
||||
ghoul_assert(keyboard, "No keyboard");
|
||||
currentPos += sizeof(std::vector<KeyboardCallback>);
|
||||
#else // ^^^ WIN32 / !WIN32 vvv
|
||||
|
||||
@@ -316,6 +316,7 @@ glm::dquat Path::linearPathRotation(double t) const {
|
||||
}
|
||||
|
||||
glm::dquat Path::lookAtTargetsRotation(double t) const {
|
||||
t = glm::clamp(t, 0.0, 1.0);
|
||||
const double t1 = 0.2;
|
||||
const double t2 = 0.8;
|
||||
|
||||
@@ -331,13 +332,15 @@ glm::dquat Path::lookAtTargetsRotation(double t) const {
|
||||
const glm::dvec3 viewDir = ghoul::viewDirection(_start.rotation());
|
||||
const glm::dvec3 inFrontOfStart = startPos + inFrontDistance * viewDir;
|
||||
|
||||
const double tScaled = ghoul::cubicEaseInOut(t / t1);
|
||||
const double tScaled = glm::clamp(t / t1, 0.0, 1.0);
|
||||
const double tEased = ghoul::cubicEaseInOut(tScaled);
|
||||
lookAtPos =
|
||||
ghoul::interpolateLinear(tScaled, inFrontOfStart, startNodePos);
|
||||
ghoul::interpolateLinear(tEased, inFrontOfStart, startNodePos);
|
||||
}
|
||||
else if (t <= t2) {
|
||||
const double tScaled = ghoul::cubicEaseInOut((t - t1) / (t2 - t1));
|
||||
lookAtPos = ghoul::interpolateLinear(tScaled, startNodePos, endNodePos);
|
||||
const double tScaled = glm::clamp((t - t1) / (t2 - t1), 0.0, 1.0);
|
||||
const double tEased = ghoul::cubicEaseInOut(tScaled);
|
||||
lookAtPos = ghoul::interpolateLinear(tEased, startNodePos, endNodePos);
|
||||
}
|
||||
else {
|
||||
// (t > t2)
|
||||
@@ -346,8 +349,9 @@ glm::dquat Path::lookAtTargetsRotation(double t) const {
|
||||
const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation());
|
||||
const glm::dvec3 inFrontOfEnd = endPos + inFrontDistance * viewDir;
|
||||
|
||||
const double tScaled = ghoul::cubicEaseInOut((t - t2) / (1.0 - t2));
|
||||
lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd);
|
||||
const double tScaled = glm::clamp((t - t2) / (1.0 - t2), 0.0, 1.0);
|
||||
const double tEased = ghoul::cubicEaseInOut(tScaled);
|
||||
lookAtPos = ghoul::interpolateLinear(tEased, endNodePos, inFrontOfEnd);
|
||||
}
|
||||
|
||||
// Handle up vector separately
|
||||
@@ -416,6 +420,7 @@ double Path::speedAlongPath(double traveledDistance) const {
|
||||
const double remainingDistance = pathLength() - traveledDistance;
|
||||
dampeningFactor = remainingDistance / closeUpDistance;
|
||||
}
|
||||
dampeningFactor = glm::clamp(dampeningFactor, 0.0, 1.0);
|
||||
dampeningFactor = ghoul::sineEaseOut(dampeningFactor);
|
||||
|
||||
// Prevent multiplying with 0 (and hence a speed of 0.0 => no movement)
|
||||
@@ -706,7 +711,7 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary,
|
||||
try {
|
||||
return Path(startPoint, waypointToAdd, type, duration);
|
||||
}
|
||||
catch (const PathCurve::TooShortPathError& e) {
|
||||
catch (const PathCurve::TooShortPathError&) {
|
||||
LINFO("Already at the requested target");
|
||||
// Rethrow e, so the pathnavigator can handle it as well
|
||||
throw;
|
||||
|
||||
@@ -267,6 +267,10 @@ bool Renderable::matchesRenderBinMask(int binMask) {
|
||||
return binMask & static_cast<int>(renderBin());
|
||||
}
|
||||
|
||||
void Renderable::setFade(float fade) {
|
||||
_fade = fade;
|
||||
}
|
||||
|
||||
bool Renderable::isVisible() const {
|
||||
return _enabled && _opacity > 0.f && _fade > 0.f;
|
||||
}
|
||||
|
||||
@@ -1099,15 +1099,14 @@ void RenderEngine::removeScreenSpaceRenderable(ScreenSpaceRenderable* s) {
|
||||
}
|
||||
}
|
||||
|
||||
void RenderEngine::removeScreenSpaceRenderable(const std::string& identifier) {
|
||||
void RenderEngine::removeScreenSpaceRenderable(std::string_view identifier) {
|
||||
ScreenSpaceRenderable* s = screenSpaceRenderable(identifier);
|
||||
if (s) {
|
||||
removeScreenSpaceRenderable(s);
|
||||
}
|
||||
}
|
||||
|
||||
ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(const std::string& identifier)
|
||||
{
|
||||
ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(std::string_view identifier) {
|
||||
const auto it = std::find_if(
|
||||
global::screenSpaceRenderables->begin(),
|
||||
global::screenSpaceRenderables->end(),
|
||||
|
||||
Reference in New Issue
Block a user