mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-04-22 19:29:04 -05:00
Solve merge conflict and changed to rendering only clipmap globe
This commit is contained in:
@@ -3,7 +3,7 @@ return {
|
||||
CommonFolder = "common",
|
||||
Camera = {
|
||||
Focus = "DebugGlobe",
|
||||
Position = {1, 0, 0, 7},
|
||||
Position = {1, 0, 0, 8},
|
||||
},
|
||||
Modules = {
|
||||
"debugglobe",
|
||||
|
||||
@@ -46,6 +46,7 @@ set(HEADER_FILES
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/chunknode.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/latlon.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/angle.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/lrucache.h
|
||||
|
||||
)
|
||||
@@ -69,6 +70,7 @@ set(SOURCE_FILES
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/chunknode.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/latlon.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/angle.inl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/datastructures/lrucache.inl
|
||||
|
||||
)
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2016 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __ANGLE_H__
|
||||
#define __ANGLE_H__
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <math.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
template <typename T>
|
||||
class Angle {
|
||||
public:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// STATIC CONSTANTS //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const T PI;
|
||||
static const T EPSILON;
|
||||
|
||||
|
||||
/** = 0 radians = 0 degrees = no revolution */
|
||||
static const Angle<T> ZERO;
|
||||
|
||||
/** = PI/2 radians = 90 degrees = quarter of a revolution */
|
||||
static const Angle<T> QUARTER;
|
||||
|
||||
/** = PI radians = 180 degrees = half a revolution */
|
||||
static const Angle<T> HALF;
|
||||
|
||||
/** = 2PI radians = 360 degrees = a full revolution */
|
||||
static const Angle<T> FULL;
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Factory Methods //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static Angle<T> fromRadians(T radians);
|
||||
static Angle<T> fromDegrees(T degrees);
|
||||
static Angle<T> fromRevolutions(T revs);
|
||||
|
||||
|
||||
public:
|
||||
/** Copy constructor */
|
||||
Angle<T>(const Angle<T>& other);
|
||||
|
||||
private:
|
||||
/** Private constructor. Use factory methods to avoid unit confusion */
|
||||
Angle<T>(T rad);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Conversions //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
public:
|
||||
inline T asRadians() const;
|
||||
inline T asDegrees() const;
|
||||
inline T asRevolutions() const;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Operators (boilerplate, I know.. /eb) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Angle<T> operator+(const Angle<T>& rhs) const;
|
||||
Angle<T> operator-(const Angle<T>& rhs) const;
|
||||
Angle<T> operator*(T rhs) const;
|
||||
Angle<T> operator/(T rhs) const;
|
||||
|
||||
Angle<T> operator-() const;
|
||||
|
||||
void operator+=(const Angle<T>& rhs);
|
||||
void operator-=(const Angle<T>& rhs);
|
||||
void operator*=(T rhs);
|
||||
void operator/=(T rhs);
|
||||
|
||||
bool operator<(const Angle<T>& rhs) const;
|
||||
bool operator<=(const Angle<T>& rhs) const;
|
||||
bool operator>(const Angle<T>& rhs) const;
|
||||
bool operator>=(const Angle<T>& rhs) const;
|
||||
bool operator==(const Angle<T>& rhs) const;
|
||||
bool operator!=(const Angle<T>& rhs) const;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chainable Relative Mutators //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Normalizes the angle to the interval [0, 2pi[
|
||||
*/
|
||||
Angle<T>& normalize();
|
||||
|
||||
/**
|
||||
* Normalizes the angle to the interval [center - pi, center + pi[
|
||||
*/
|
||||
Angle<T>& normalizeAround(const Angle<T>& center);
|
||||
|
||||
/**
|
||||
* Clamps the angle to the interval [min, max].
|
||||
* Default arguments are [0, 2pi].
|
||||
*/
|
||||
Angle<T>& clamp(const Angle<T>& min = ZERO, const Angle<T>& max = FULL);
|
||||
|
||||
|
||||
Angle<T>& abs();
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chainable Relative Factory Methods //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns a new angle normalized to the interval [0, 2pi[
|
||||
*/
|
||||
Angle<T> getNormalized() const;
|
||||
|
||||
/**
|
||||
* Returns a new angle normalized to the interval [center - pi, center + pi[
|
||||
*/
|
||||
Angle<T> getNormalizedAround(const Angle<T>& center) const;
|
||||
|
||||
/**
|
||||
* Returns a new angle clamped to the interval [min, max].
|
||||
* Default arguments are [0, 2pi].
|
||||
*/
|
||||
Angle<T> getClamped(const Angle<T>& min = ZERO, const Angle<T>& max = FULL) const;
|
||||
|
||||
|
||||
Angle<T> getAbs() const;
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
T _radians;
|
||||
|
||||
};
|
||||
|
||||
using dAngle = Angle<double>;
|
||||
using fAngle = Angle<float>;
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
|
||||
|
||||
#include <modules/globebrowsing/datastructures/angle.inl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // __ANGLE_H__
|
||||
@@ -0,0 +1,268 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2016 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <modules/globebrowsing/datastructures/angle.h>
|
||||
|
||||
|
||||
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
template <typename T>
|
||||
const T Angle<T>::PI = 3.14159265358979323846264338327950;
|
||||
|
||||
template <typename T>
|
||||
const T Angle<T>::EPSILON = 1e-10; // Should depend on the typedef /eb
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// STATIC CONSTANTS //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
const Angle<T> Angle<T>::ZERO = Angle::fromRadians(0);
|
||||
|
||||
template <typename T>
|
||||
const Angle<T> Angle<T>::QUARTER = Angle::fromRadians(PI/2);
|
||||
|
||||
template <typename T>
|
||||
const Angle<T> Angle<T>::HALF = Angle::fromRadians(PI);
|
||||
|
||||
template <typename T>
|
||||
const Angle<T> Angle<T>::FULL = Angle::fromRadians(2*PI);
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructors //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T> Angle<T>::Angle(const Angle<T>& other)
|
||||
: _radians(other._radians) { }
|
||||
|
||||
template <typename T> Angle<T>::Angle(T radians)
|
||||
: _radians(radians) { }
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Factory Methods //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::fromRadians(T rads) {
|
||||
return Angle<T>(rads);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::fromDegrees(T degrees) {
|
||||
return Angle<T>(degrees * PI / 180.0);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::fromRevolutions(T revs) {
|
||||
return Angle<T>(asDegrees * 2 * PI);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Conversions //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
T Angle<T>::asRadians() const {
|
||||
return _radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Angle<T>::asDegrees() const {
|
||||
return _radians * 180.0 / PI;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Angle<T>::asRevolutions() const {
|
||||
return _radians / (2 * PI);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Operators //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::operator+(const Angle<T>& rhs) const{
|
||||
return Angle<T>(_radians + rhs._radians);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::operator-(const Angle<T>& rhs) const{
|
||||
return Angle<T>(_radians - rhs._radians);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::operator*(T multiplier) const{
|
||||
return Angle<T>(_radians * multiplier);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::operator/(T divisor) const{
|
||||
return Angle<T>(_radians / divisor);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::operator-() const {
|
||||
return Angle<T>(-_radians);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void Angle<T>::operator+=(const Angle<T>& rhs){
|
||||
_radians += rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Angle<T>::operator-=(const Angle<T>& rhs){
|
||||
_radians -= rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Angle<T>::operator*=(T multiplier){
|
||||
_radians *= multiplier;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Angle<T>::operator/=(T divisor){
|
||||
_radians /= divisor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator<(const Angle<T>& rhs) const{
|
||||
return _radians < rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator<=(const Angle<T>& rhs) const{
|
||||
return _radians <= rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator>(const Angle<T>& rhs) const{
|
||||
return _radians > rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator>=(const Angle<T>& rhs) const{
|
||||
return _radians >= rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator==(const Angle<T>& rhs) const{
|
||||
return _radians == rhs._radians;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Angle<T>::operator!=(const Angle<T>& rhs) const{
|
||||
return _radians != rhs._radians;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chainable Relative Mutators //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
Angle<T>& Angle<T>::normalize() {
|
||||
// this will cause _radians to be in value range ]-2pi, 2pi[
|
||||
_radians = fmod(_radians, 2*PI);
|
||||
|
||||
// ensure _radians are positive, ie in value range [0, 2pi[
|
||||
if (_radians < 0.0){
|
||||
_radians += 2 * PI;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T>& Angle<T>::normalizeAround(const Angle<T>& center) {
|
||||
_radians -= center._radians + PI;
|
||||
normalize();
|
||||
_radians += center._radians - PI;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T>& Angle<T>::clamp(const Angle<T>& min, const Angle<T>& max) {
|
||||
_radians = _radians < min._radians ? min._radians
|
||||
: _radians > max._radians ? max._radians
|
||||
: _radians;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
Angle<T>& Angle<T>::abs(){
|
||||
_radians = glm::abs(_radians);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Chainable Relative Factory Methods //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::getNormalized() const {
|
||||
return Angle<T>(*this).normalize();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::getNormalizedAround(const Angle<T>& center) const {
|
||||
return Angle<T>(*this).normalizeAround(center);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::getClamped(const Angle<T>& min, const Angle<T>& max) const {
|
||||
return Angle<T>(*this).clamp(min, max);
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
Angle<T> Angle<T>::getAbs() const {
|
||||
return Angle<T>(*this).abs();
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -39,6 +39,7 @@ namespace {
|
||||
namespace openspace {
|
||||
|
||||
int ChunkNode::instanceCount = 0;
|
||||
int ChunkNode::renderedPatches = 0;
|
||||
|
||||
ChunkNode::ChunkNode(ChunkLodGlobe& owner, const LatLonPatch& patch, ChunkNode* parent)
|
||||
: _owner(owner)
|
||||
@@ -83,6 +84,7 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data, ChunkIndex& trav
|
||||
|
||||
if (isLeaf()) {
|
||||
int desiredLevel = calculateDesiredLevel(data, traverseData);
|
||||
desiredLevel = glm::clamp(desiredLevel, _owner.minSplitDepth, _owner.maxSplitDepth);
|
||||
if (desiredLevel > traverseData.level) {
|
||||
split();
|
||||
}
|
||||
@@ -100,7 +102,6 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data, ChunkIndex& trav
|
||||
requestedMergeMask |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check if all children requested merge
|
||||
if (requestedMergeMask == 0xf) {
|
||||
@@ -116,9 +117,14 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data, ChunkIndex& trav
|
||||
|
||||
void ChunkNode::internalRender(const RenderData& data, ChunkIndex& traverseData) {
|
||||
if (isLeaf()) {
|
||||
|
||||
TileIndex ti = { traverseData.x, traverseData.y, traverseData.level };
|
||||
|
||||
LatLonPatchRenderer& patchRenderer = _owner.getPatchRenderer();
|
||||
|
||||
patchRenderer.renderPatch(_patch, data, _owner.globeRadius);
|
||||
patchRenderer.renderPatch(_patch, data, _owner.globeRadius, ti);
|
||||
ChunkNode::renderedPatches++;
|
||||
|
||||
}
|
||||
else {
|
||||
std::vector<ChunkIndex> childIndices = traverseData.childIndices();
|
||||
@@ -131,8 +137,9 @@ void ChunkNode::internalRender(const RenderData& data, ChunkIndex& traverseData)
|
||||
int ChunkNode::calculateDesiredLevel(const RenderData& data, const ChunkIndex& traverseData) {
|
||||
|
||||
|
||||
Vec3 globePosition = data.position.dvec3();
|
||||
Vec3 patchNormal = _patch.center().asUnitCartesian();
|
||||
Vec3 patchPosition = data.position.dvec3() + _owner.globeRadius * patchNormal;
|
||||
Vec3 patchPosition = globePosition + _owner.globeRadius * patchNormal;
|
||||
|
||||
Vec3 cameraPosition = data.camera.position().dvec3();
|
||||
Vec3 cameraDirection = Vec3(data.camera.viewDirection());
|
||||
@@ -141,8 +148,21 @@ int ChunkNode::calculateDesiredLevel(const RenderData& data, const ChunkIndex& t
|
||||
|
||||
// if camera points at same direction as latlon patch normal,
|
||||
// we see the back side and dont have to split it
|
||||
Scalar cosNormalCameraDirection = glm::dot(patchNormal, cameraDirection);
|
||||
if (cosNormalCameraDirection > 0.3) {
|
||||
//Scalar cosNormalCameraDirection = glm::dot(patchNormal, cameraDirection);
|
||||
|
||||
Vec3 globeToCamera = cameraPosition - globePosition;
|
||||
|
||||
LatLon cameraPositionOnGlobe = LatLon::fromCartesian(globeToCamera);
|
||||
LatLon closestPatchPoint = _patch.closestPoint(cameraPositionOnGlobe);
|
||||
|
||||
Vec3 normalOfClosestPatchPoint = closestPatchPoint.asUnitCartesian();
|
||||
Scalar cosPatchNormalNormalizedGlobeToCamera = glm::dot(normalOfClosestPatchPoint, glm::normalize(globeToCamera));
|
||||
|
||||
//LDEBUG(cosPatchNormalCameraDirection);
|
||||
|
||||
double cosAngleToHorizon = _owner.globeRadius / glm::length(globeToCamera);
|
||||
|
||||
if (cosPatchNormalNormalizedGlobeToCamera < cosAngleToHorizon) {
|
||||
return traverseData.level - 1;
|
||||
}
|
||||
|
||||
@@ -160,8 +180,8 @@ int ChunkNode::calculateDesiredLevel(const RenderData& data, const ChunkIndex& t
|
||||
|
||||
Scalar scaleFactor = 100 * _owner.globeRadius;
|
||||
Scalar projectedScaleFactor = scaleFactor / distance;
|
||||
int desiredDepth = floor( log2(projectedScaleFactor) );
|
||||
return glm::clamp(desiredDepth, _owner.minSplitDepth, _owner.maxSplitDepth);
|
||||
int desiredLevel = floor( log2(projectedScaleFactor) );
|
||||
return desiredLevel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
void render(const RenderData& data, ChunkIndex);
|
||||
|
||||
static int instanceCount;
|
||||
static int renderedPatches;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
|
||||
#include <modules/globebrowsing/datastructures/chunknode.h>
|
||||
#include <modules/globebrowsing/datastructures/latlon.h>
|
||||
#include <modules/globebrowsing/datastructures/angle.h>
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
namespace {
|
||||
const std::string _loggerCat = "LatLon";
|
||||
@@ -71,10 +75,27 @@ namespace openspace {
|
||||
return Vec2(lon, lat);
|
||||
}
|
||||
|
||||
bool LatLon::operator==(const LatLon& other) {
|
||||
bool LatLon::operator==(const LatLon& other) const {
|
||||
return lat == other.lat && lon == other.lon;
|
||||
}
|
||||
|
||||
LatLon LatLon ::operator+(const LatLon& other) const {
|
||||
return LatLon(lat - other.lat, lon - other.lon);
|
||||
}
|
||||
|
||||
LatLon LatLon ::operator-(const LatLon& other) const {
|
||||
return LatLon(lat - other.lat, lon - other.lon);
|
||||
}
|
||||
|
||||
LatLon LatLon::operator*(Scalar scalar) const {
|
||||
return LatLon(lat * scalar, lon * scalar);
|
||||
}
|
||||
|
||||
LatLon LatLon::operator/(Scalar scalar) const {
|
||||
return LatLon(lat / scalar, lon / scalar);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// LATITUDE LONGITUDE PATCH //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -133,8 +154,6 @@ namespace openspace {
|
||||
return LatLon(2 * _halfSize.lat, 2 * _halfSize.lon);
|
||||
}
|
||||
|
||||
|
||||
|
||||
LatLon LatLonPatch::northWestCorner() const{
|
||||
return LatLon(_center.lat + _halfSize.lat, _center.lon - _halfSize.lon);
|
||||
}
|
||||
@@ -151,4 +170,127 @@ namespace openspace {
|
||||
return LatLon(_center.lat - _halfSize.lat, _center.lon + _halfSize.lon);
|
||||
}
|
||||
|
||||
|
||||
LatLon LatLonPatch::clamp(const LatLon& p) const {
|
||||
using Ang = Angle<Scalar>;
|
||||
|
||||
// Convert to Angles for normalization
|
||||
Ang centerLat = Ang::fromRadians(_center.lat);
|
||||
Ang centerLon = Ang::fromRadians(_center.lon);
|
||||
Ang pointLat = Ang::fromRadians(p.lat);
|
||||
Ang pointLon = Ang::fromRadians(p.lon);
|
||||
|
||||
// Normalize w.r.t. the center in order for the clamping to done correctly
|
||||
//
|
||||
// Example:
|
||||
// centerLat = 0 deg, halfSize.lat = 10 deg, pointLat = 330 deg
|
||||
// --> Just clamping pointLat would be clamp(330, -10, 10) = 10 // WRONG!
|
||||
// Instead, if we first normalize 330 deg around 0, we get -30 deg
|
||||
// --> clamp(-30, -10, 10) = -10 // CORRECT!
|
||||
pointLat.normalizeAround(centerLat);
|
||||
pointLon.normalizeAround(centerLon);
|
||||
|
||||
// get clamp bounds
|
||||
LatLon max = northEastCorner();
|
||||
LatLon min = southWestCorner();
|
||||
|
||||
return LatLon(
|
||||
glm::clamp(pointLat.asRadians(), min.lat, max.lat),
|
||||
glm::clamp(pointLon.asRadians(), min.lon, max.lon)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
LatLon LatLonPatch::closestCorner(const LatLon& p) const {
|
||||
using Ang = Angle<Scalar>;
|
||||
|
||||
// LatLon vector from patch center to the point
|
||||
LatLon centerToPoint = p - _center;
|
||||
|
||||
// Normalize the difference angles to be centered around 0.
|
||||
Ang latDiff = Ang::fromRadians(centerToPoint.lat).normalizeAround(Ang::ZERO);
|
||||
Ang lonDiff = Ang::fromRadians(centerToPoint.lon).normalizeAround(Ang::ZERO);
|
||||
|
||||
// If latDiff > 0
|
||||
// --> point p is north of the patch center
|
||||
// --> the closest corner to the point must be a northern one
|
||||
// --> set the corner's latitude coordinate to center.lat + halfSize.lat
|
||||
// else
|
||||
// --> set corner's latidude coordinate to center.lat - halfSize.lat
|
||||
Scalar cornerLat = _center.lat + _halfSize.lat * (latDiff > Ang::ZERO ? 1 : -1);
|
||||
|
||||
// We then assigned the corner's longitude coordinate in a similar fashion
|
||||
Scalar cornerLon = _center.lon + _halfSize.lon * (lonDiff > Ang::ZERO ? 1 : -1);
|
||||
|
||||
return LatLon(cornerLat, cornerLon);
|
||||
}
|
||||
|
||||
|
||||
LatLon LatLonPatch::closestPoint(const LatLon& p) const {
|
||||
// This method finds the closest point on the patch, to the provided
|
||||
// point p. As we are deali ng with latitude-longitude patches, distance in this
|
||||
// context refers to great-circle distance.
|
||||
// (https://en.wikipedia.org/wiki/Great-circle_distance)
|
||||
//
|
||||
// This uses a simple clamping approach to find the closest point on the
|
||||
// patch. A naive castesian clamp is not sufficient for this purpose,
|
||||
// as illustrated with an example below.
|
||||
|
||||
// Example: (degrees are used for latidude, longitude)
|
||||
// patchCenter = (0,0), patchHalfSize = (45,45), point = (5, 170)
|
||||
// Note, the point and the patch are on opposite sides of the sphere
|
||||
//
|
||||
// cartesian clamp:
|
||||
// --> clampedPointLat = clamp(5, -45, 45) = 5
|
||||
// --> clampedPointLon = clamp(170, -45, 45) = 45
|
||||
// --> result: (5, 45)
|
||||
// --> closest point is actually (45, 45)
|
||||
// --> The error is significant
|
||||
//
|
||||
// This method simply adds an extra clamp on the latitude in these cases. In the
|
||||
// above example, that would be the following:
|
||||
// --> clampedPointLat = clamp(180 - 5, -45, 45) = 45
|
||||
//
|
||||
// Just doing this actually makes points returned from this methods being the
|
||||
// true closest point, great-circle distance-wise.
|
||||
|
||||
|
||||
using Ang = Angle<Scalar>;
|
||||
|
||||
// Convert to Angles for normalization
|
||||
Ang centerLat = Ang::fromRadians(_center.lat);
|
||||
Ang centerLon = Ang::fromRadians(_center.lon);
|
||||
Ang pointLat = Ang::fromRadians(p.lat);
|
||||
Ang pointLon = Ang::fromRadians(p.lon);
|
||||
|
||||
// Normalize point with respect to center. This is done because the point
|
||||
// will later be clamped. See LatLonPatch::clamp(const LatLon&) for explanation
|
||||
pointLat.normalizeAround(centerLat);
|
||||
pointLon.normalizeAround(centerLon);
|
||||
|
||||
// Calculate the longitud difference between center and point. We normalize around
|
||||
// zero because we want the "shortest distance" difference, i.e the difference
|
||||
// should be in the interval [-180 deg, 180 deg]
|
||||
Ang centerToPointLon = (centerLon - pointLon).normalizeAround(Ang::ZERO);
|
||||
|
||||
// Calculate the longitudinal distance to the closest patch edge
|
||||
Ang longitudeDistanceToClosestPatchEdge = centerToPointLon.abs() - Ang::fromRadians(_halfSize.lon);
|
||||
|
||||
// get clamp bounds
|
||||
LatLon max = northEastCorner();
|
||||
LatLon min = southWestCorner();
|
||||
|
||||
// If the longitude distance to the closest patch edge is larger than 90 deg
|
||||
// the latitude will have to be clamped to its closest corner, as explained in
|
||||
// the example above.
|
||||
Scalar clampedLat = longitudeDistanceToClosestPatchEdge > Ang::QUARTER ?
|
||||
clampedLat = glm::clamp((Ang::HALF - pointLat).normalizeAround(centerLat).asRadians(), min.lat, max.lat) :
|
||||
clampedLat = glm::clamp(pointLat.asRadians(), min.lat, max.lat);
|
||||
|
||||
// Longitude is just clamped normally
|
||||
Scalar clampedLon = glm::clamp(pointLon.asRadians(), min.lon, max.lon);
|
||||
|
||||
return LatLon(clampedLat, clampedLon);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -38,6 +38,10 @@ typedef glm::dvec3 Vec3;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct LatLon {
|
||||
LatLon();
|
||||
LatLon(Scalar latitude, Scalar longitude);
|
||||
@@ -47,8 +51,13 @@ struct LatLon {
|
||||
Vec3 asUnitCartesian() const;
|
||||
Vec2 toLonLatVec2() const;
|
||||
|
||||
inline bool operator==(const LatLon& other);
|
||||
inline bool operator!=(const LatLon& other) { return !(*this == (other)); }
|
||||
inline bool operator==(const LatLon& other) const;
|
||||
inline bool operator!=(const LatLon& other) const { return !(*this == (other)); }
|
||||
|
||||
inline LatLon operator+(const LatLon& other) const;
|
||||
inline LatLon operator-(const LatLon& other) const;
|
||||
inline LatLon operator*(Scalar scalar) const;
|
||||
inline LatLon operator/(Scalar scalar) const;
|
||||
|
||||
Scalar lat;
|
||||
Scalar lon;
|
||||
@@ -85,6 +94,23 @@ public:
|
||||
LatLon southWestCorner() const;
|
||||
LatLon southEastCorner() const;
|
||||
|
||||
/**
|
||||
* Clamps a point to the patch region
|
||||
*/
|
||||
LatLon clamp(const LatLon& p) const;
|
||||
|
||||
/**
|
||||
* Returns the corner of the patch that is closest to the given point p
|
||||
*/
|
||||
LatLon closestCorner(const LatLon& p) const;
|
||||
|
||||
/**
|
||||
* Returns a point on the patch that minimizes the great-circle distance to
|
||||
* the given point p.
|
||||
*/
|
||||
LatLon closestPoint(const LatLon& p) const;
|
||||
|
||||
|
||||
const LatLon& center() const;
|
||||
const LatLon& halfSize() const;
|
||||
LatLon size() const;
|
||||
|
||||
@@ -38,7 +38,9 @@ namespace openspace {
|
||||
: _cacheSize(size) { }
|
||||
|
||||
template<typename KeyType, typename ValueType>
|
||||
LRUCache<KeyType, ValueType>::~LRUCache() { }
|
||||
LRUCache<KeyType, ValueType>::~LRUCache() {
|
||||
// Clean up list and map!
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
@@ -69,7 +71,7 @@ namespace openspace {
|
||||
template<typename KeyType, typename ValueType>
|
||||
ValueType LRUCache<KeyType, ValueType>::get(const KeyType& key)
|
||||
{
|
||||
ghoul_assert(exist(key), "Key " << key << " must exists");
|
||||
ghoul_assert(exist(key), "Key " << key << " must exist");
|
||||
auto it = _itemMap.find(key);
|
||||
// Move list iterator pointing to value
|
||||
_itemList.splice(_itemList.begin(), _itemList, it->second);
|
||||
|
||||
@@ -124,6 +124,8 @@ namespace openspace {
|
||||
|
||||
void ChunkLodGlobe::render(const RenderData& data){
|
||||
minDistToCamera = INFINITY;
|
||||
ChunkNode::renderedPatches = 0;
|
||||
|
||||
ChunkIndex leftRootTileIndex = { 0, 0, 1 };
|
||||
_leftRoot->render(data, leftRootTileIndex);
|
||||
|
||||
@@ -133,9 +135,10 @@ namespace openspace {
|
||||
//LDEBUG("min distnace to camera: " << minDistToCamera);
|
||||
|
||||
Vec3 cameraPos = data.camera.position().dvec3();
|
||||
LDEBUG("cam pos x: " << cameraPos.x << " y: " << cameraPos.y << " z: " << cameraPos.z);
|
||||
//LDEBUG("cam pos x: " << cameraPos.x << " y: " << cameraPos.y << " z: " << cameraPos.z);
|
||||
|
||||
//LDEBUG("ChunkNode count: " << ChunkNode::instanceCount);
|
||||
//LDEBUG("RenderedPatches count: " << ChunkNode::renderedPatches);
|
||||
}
|
||||
|
||||
void ChunkLodGlobe::update(const UpdateData& data) {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#include <modules/globebrowsing/datastructures/chunknode.h>
|
||||
#include <modules/globebrowsing/rendering/patchrenderer.h>
|
||||
#include <modules/globebrowsing/rendering/twmstileprovider.h>
|
||||
|
||||
namespace ghoul {
|
||||
namespace opengl {
|
||||
@@ -93,6 +94,7 @@ namespace openspace {
|
||||
static const LatLonPatch RIGHT_HEMISPHERE;
|
||||
|
||||
properties::IntProperty _rotation;
|
||||
|
||||
|
||||
glm::dmat3 _stateMatrix;
|
||||
std::string _frame;
|
||||
|
||||
@@ -98,17 +98,16 @@ namespace openspace {
|
||||
renderPatch(patch, data, radius, ti);
|
||||
}
|
||||
|
||||
void LatLonPatchRenderer::renderPatch(
|
||||
const LatLonPatch& patch,
|
||||
const RenderData& data,
|
||||
double radius,
|
||||
const TileIndex& tileIndex)
|
||||
void LatLonPatchRenderer::renderPatch(const LatLonPatch& patch,const RenderData& data,
|
||||
double radius, const TileIndex& tileIndex)
|
||||
{
|
||||
|
||||
using namespace glm;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO : Model transform should be fetched as a matrix directly.
|
||||
mat4 modelTransform = translate(mat4(1), data.position.vec3());
|
||||
mat4 viewTransform = data.camera.combinedViewMatrix();
|
||||
@@ -120,16 +119,26 @@ namespace openspace {
|
||||
_programObject->activate();
|
||||
|
||||
// Get the textures that should be used for rendering
|
||||
LatLonPatch tilePatch = _tileSet.getTilePositionAndScale(tileIndex);
|
||||
std::shared_ptr<ghoul::opengl::Texture> tile00 = _tileSet.getTile(tileIndex);
|
||||
glm::mat3 uvTransform = _tileSet.getUvTransformationPatchToTile(patch, tileIndex);
|
||||
std::shared_ptr<ghoul::opengl::Texture> tile00;
|
||||
bool usingTile = true;
|
||||
TileIndex ti;
|
||||
ti.level =tileIndex.level;
|
||||
ti.x = tileIndex.y;
|
||||
ti.y = tileIndex.x;
|
||||
tile00 = tileProvider.getTile(ti);
|
||||
|
||||
if (tile00 == nullptr) {
|
||||
tile00 = _tileSet.getTile(tileIndex);
|
||||
usingTile = false;
|
||||
}
|
||||
|
||||
glm::mat3 uvTransform = usingTile ? glm::mat3(1) : _tileSet.getUvTransformationPatchToTile(patch, tileIndex);
|
||||
|
||||
// Bind and use the texture
|
||||
ghoul::opengl::TextureUnit texUnit;
|
||||
texUnit.activate();
|
||||
tile00->bind();
|
||||
_programObject->setUniform("textureSampler", texUnit);
|
||||
|
||||
_programObject->setUniform("uvTransformPatchToTile", uvTransform);
|
||||
|
||||
LatLon swCorner = patch.southWestCorner();
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
namespace ghoul {
|
||||
namespace opengl {
|
||||
class ProgramObject;
|
||||
class ProgramObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace openspace {
|
||||
const TileIndex& ti);
|
||||
private:
|
||||
shared_ptr<Grid> _grid;
|
||||
TwmsTileProvider tileProvider;
|
||||
};
|
||||
|
||||
|
||||
@@ -101,6 +102,7 @@ namespace openspace {
|
||||
private:
|
||||
shared_ptr<ClipMapGrid> _grid;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __LATLONPATCH_H__
|
||||
@@ -66,8 +66,8 @@ namespace openspace {
|
||||
// Mainly for debugging purposes @AA
|
||||
addProperty(_rotation);
|
||||
|
||||
addSwitchValue(std::shared_ptr<ClipMapGlobe>(new ClipMapGlobe(dictionary)), 1e7);
|
||||
addSwitchValue(std::shared_ptr<ChunkLodGlobe>(new ChunkLodGlobe(dictionary)), 1e9);
|
||||
addSwitchValue(std::shared_ptr<ClipMapGlobe>(new ClipMapGlobe(dictionary)), 1e9);
|
||||
//addSwitchValue(std::shared_ptr<ChunkLodGlobe>(new ChunkLodGlobe(dictionary)), 1e9);
|
||||
addSwitchValue(std::shared_ptr<GlobeMesh>(new GlobeMesh(dictionary)), 1e10);
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,11 @@ namespace openspace {
|
||||
|
||||
|
||||
// Set e texture to test
|
||||
_testTexture = std::move(ghoul::io::TextureReader::ref().loadTexture(absPath("textures/earth_bluemarble.jpg")));
|
||||
std::string fileName = "textures/earth_bluemarble.jpg";
|
||||
//std::string fileName = "../../../build/tiles/tile5_8_12.png";
|
||||
//std::string fileName = "tile5_8_12.png";
|
||||
_testTexture = std::move(ghoul::io::TextureReader::ref().loadTexture(absPath(fileName)));
|
||||
|
||||
if (_testTexture) {
|
||||
LDEBUG("Loaded texture from '" << "textures/earth_bluemarble.jpg" << "'");
|
||||
_testTexture->uploadTexture();
|
||||
|
||||
@@ -168,8 +168,11 @@ void TriangleSoup::drawUsingActiveProgram() {
|
||||
|
||||
glBindVertexArray(_vaoID);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _elementBufferID);
|
||||
//glEnable(GL_CULL_FACE);
|
||||
//glCullFace(GL_FRONT);
|
||||
glDrawElements(GL_TRIANGLES, _elementData.size(), GL_UNSIGNED_INT, 0);
|
||||
glBindVertexArray(0);
|
||||
//glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -21,7 +21,7 @@
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
/*
|
||||
|
||||
|
||||
#include <modules/globebrowsing/rendering/twmstileprovider.h>
|
||||
|
||||
@@ -43,10 +43,14 @@ namespace {
|
||||
namespace openspace {
|
||||
|
||||
TwmsTileProvider::TwmsTileProvider()
|
||||
: _tileCache(500) // setting cache size
|
||||
: _tileCache(5000) // setting cache size
|
||||
, _fileFutureCache(5000) // setting cache size
|
||||
{
|
||||
int downloadApplicationVersion = 1;
|
||||
DownloadManager::initialize("../tmp_openspace_downloads/", downloadApplicationVersion);
|
||||
if (!DownloadManager::isInitialized()) {
|
||||
DownloadManager::initialize("../tmp_openspace_downloads/", downloadApplicationVersion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TwmsTileProvider::~TwmsTileProvider(){
|
||||
@@ -59,32 +63,66 @@ namespace openspace {
|
||||
if (_tileCache.exist(hashkey)) {
|
||||
return _tileCache.get(hashkey);
|
||||
}
|
||||
else if (_fileFutureCache.exist(hashkey)) {
|
||||
if (_fileFutureCache.get(hashkey)->isFinished) {
|
||||
std::string fileName = _fileFutureCache.get(hashkey)->filePath;
|
||||
std::string filePath = "tiles/" + fileName;
|
||||
std::shared_ptr<Texture> texture = loadAndInitTextureDisk(filePath);
|
||||
LDEBUG("Downloaded " << fileName);
|
||||
_tileCache.put(hashkey, texture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
downloadTileAndPutInCache(tileIndex);
|
||||
std::shared_ptr<DownloadManager::FileFuture> fileFuture = requestTile(tileIndex);
|
||||
_fileFutureCache.put(hashkey, fileFuture);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> TwmsTileProvider::loadAndInitTextureDisk(std::string filePath) {
|
||||
auto textureReader = ghoul::io::TextureReader::ref();
|
||||
std::shared_ptr<Texture> texture = std::move(textureReader.loadTexture(absPath(filePath)));
|
||||
|
||||
// upload to gpu
|
||||
texture->uploadTexture();
|
||||
texture->setFilter(ghoul::opengl::Texture::FilterMode::Linear);
|
||||
texture->setWrapping(ghoul::opengl::Texture::WrappingMode::ClampToEdge);
|
||||
return texture;
|
||||
}
|
||||
|
||||
|
||||
void TwmsTileProvider::downloadTileAndPutInCache(const TileIndex& tileIndex) {
|
||||
|
||||
std::shared_ptr<DownloadManager::FileFuture> TwmsTileProvider::requestTile(const TileIndex& tileIndex) {
|
||||
// download tile
|
||||
std::stringstream ss;
|
||||
std::string baseUrl = "https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi?TIME=2016-04-17&layer=MODIS_Terra_CorrectedReflectance_TrueColor&tilematrixset=EPSG4326_250m&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fjpeg";
|
||||
//std::string baseUrl = "https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi?TIME=2016-04-17&layer=MODIS_Terra_CorrectedReflectance_TrueColor&tilematrixset=EPSG4326_250m&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fjpeg";
|
||||
//ss << baseUrl;
|
||||
//ss << "&TileMatrix=" << tileIndex.level;
|
||||
//ss << "&TileCol=" << tileIndex.x;
|
||||
//ss << "&TileRow=" << tileIndex.y;
|
||||
// https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi?TIME=2016-04-17&layer=MODIS_Terra_CorrectedReflectance_TrueColor&tilematrixset=EPSG4326_250m&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fjpeg&TileMatrix=0&TileCol=0&TileRow=0
|
||||
|
||||
std::string baseUrl = "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913";
|
||||
ss << baseUrl;
|
||||
ss << "&TileMatrix=" << tileIndex.level;
|
||||
ss << "&TileCol=" << tileIndex.x;
|
||||
ss << "&TileRow=" << tileIndex.y;
|
||||
ss << "/" << tileIndex.level;
|
||||
ss << "/" << tileIndex.x;
|
||||
ss << "/" << tileIndex.y;
|
||||
ss << ".png?1461277159335";
|
||||
// http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/5/8/12.png?
|
||||
std::string twmsRequestUrl = ss.str();
|
||||
|
||||
ss = std::stringstream();
|
||||
ss << tileIndex.level;
|
||||
ss << "_" << tileIndex.x;
|
||||
ss << "_" << tileIndex.y;
|
||||
std::string filePath = "tiles/tile" + ss.str() + ".png";
|
||||
|
||||
using ghoul::filesystem::File;
|
||||
|
||||
std::string filename = "tiles/tile" + twmsRequestUrl.substr(baseUrl.length()) + ".jpg";
|
||||
File localTileFile(filename);
|
||||
File localTileFile(filePath);
|
||||
bool overrideFile = true;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
struct OnTileDownloaded {
|
||||
HashKey key;
|
||||
LRUCache<HashKey, std::shared_ptr<Texture>> * tileCache;
|
||||
@@ -97,21 +135,26 @@ namespace openspace {
|
||||
}
|
||||
|
||||
void operator()(const DownloadManager::FileFuture& ff) const {
|
||||
LDEBUG("Download of tile with hashkey " << key << " done!");
|
||||
//LDEBUG("Download of tile with hashkey " << key << " done!");
|
||||
auto textureReader = ghoul::io::TextureReader::ref();
|
||||
std::shared_ptr<Texture> texture = std::move(textureReader.loadTexture(absPath(ff.filePath)));
|
||||
std::string relFilePath = "tiles/" + ff.filePath;
|
||||
std::shared_ptr<Texture> texture = std::move(textureReader.loadTexture(absPath(relFilePath)));
|
||||
|
||||
// upload to gpu
|
||||
texture->uploadTexture();
|
||||
texture->setFilter(ghoul::opengl::Texture::FilterMode::Linear);
|
||||
|
||||
tileCache->put(key, texture);
|
||||
LDEBUG("Cache updated");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
OnTileDownloaded onTileDownloaded(tileIndex.hashKey(), &_tileCache);
|
||||
*/
|
||||
|
||||
std::shared_ptr<DownloadManager::FileFuture> ff = DownloadManager::ref().downloadFile(twmsRequestUrl, localTileFile, overrideFile, onTileDownloaded);
|
||||
std::shared_ptr<DownloadManager::FileFuture> fileFuture = DownloadManager::ref().downloadFile(twmsRequestUrl, localTileFile, overrideFile);
|
||||
return fileFuture;
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
*/
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/opengl/texture.h>
|
||||
|
||||
#include <openspace/engine/downloadmanager.h>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +49,6 @@ namespace openspace {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#include <modules/globebrowsing/datastructures/lrucache.h>
|
||||
|
||||
|
||||
@@ -56,7 +57,7 @@ namespace openspace {
|
||||
// TWMS TILE PROVIDER //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
|
||||
namespace openspace {
|
||||
using namespace ghoul::opengl;
|
||||
|
||||
@@ -70,11 +71,13 @@ namespace openspace {
|
||||
|
||||
|
||||
private:
|
||||
void downloadTileAndPutInCache(const TileIndex&);
|
||||
std::shared_ptr<DownloadManager::FileFuture> requestTile(const TileIndex&);
|
||||
std::shared_ptr<Texture> loadAndInitTextureDisk(std::string filePath);
|
||||
|
||||
LRUCache<HashKey, std::shared_ptr<Texture>> _tileCache;
|
||||
LRUCache<HashKey, std::shared_ptr<DownloadManager::FileFuture>> _fileFutureCache;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
*/
|
||||
|
||||
#endif // __TWMS_TILE_PROVIDER_H__
|
||||
@@ -58,6 +58,7 @@ void main()
|
||||
vec3 p = globalInterpolation();
|
||||
|
||||
vec4 position = modelViewProjectionTransform * vec4(p, 1);
|
||||
|
||||
vs_position = z_normalization(position);
|
||||
gl_Position = vs_position;
|
||||
}
|
||||
@@ -51,7 +51,11 @@ Fragment getFragment() {
|
||||
Fragment frag;
|
||||
|
||||
frag.color = texture(textureSampler, vec2(uvTransformPatchToTile * vec3(vs_uv.s, vs_uv.t, 1)));
|
||||
frag.color = frag.color * 1.0 + vec4(fract(vs_uv * segmentsPerPatch), 0.4,1) * 0.4;
|
||||
//frag.color = 0.001*frag.color + 0.999*texture(textureSampler, vs_uv);
|
||||
|
||||
vec4 uvColor = vec4(fract(vs_uv * segmentsPerPatch), 0.4,1);
|
||||
frag.color = frag.color.a < 0.1 ? uvColor * 0.5 : frag.color;
|
||||
|
||||
frag.depth = pscDepth(vs_position);
|
||||
|
||||
return frag;
|
||||
|
||||
@@ -619,7 +619,7 @@ void RenderablePlanetProjection::loadTexture() {
|
||||
if (_colorTexturePath.value() != "") {
|
||||
_textureOriginal = ghoul::io::TextureReader::ref().loadTexture(_colorTexturePath);
|
||||
if (_textureOriginal) {
|
||||
ghoul::opengl::convertTextureFormat(Texture::Format::RGB, *_texture);
|
||||
ghoul::opengl::convertTextureFormat(Texture::Format::RGB, *_textureOriginal);
|
||||
|
||||
_textureOriginal->uploadTexture();
|
||||
_textureOriginal->setFilter(Texture::FilterMode::Linear);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from urllib.request import urlopen, urlretrieve
|
||||
import re
|
||||
|
||||
def downloadPage(pageNumber):
|
||||
downloadURL = 'http://pluto.jhuapl.edu/soc/Pluto-Encounter/index.php?order=dateTaken&page='
|
||||
|
||||
response = urlopen(downloadURL + str(pageNumber))
|
||||
source = str(response.read())
|
||||
|
||||
searchStr = 'col-xs-2 thumbBox'
|
||||
imagePositions = [m.end() for m in re.finditer(searchStr, source)]
|
||||
|
||||
# Entire line:
|
||||
# div class="col-xs-2 thumbBox"><A HREF="view_obs.php?image=029912/lor_0299127173_0x630_sci_3.jpg&utc_time=2015-07-13<br>21:00:54 UTC&description=&target=PLUTO&range=0.7M km&exposure=150 msec&imgType=approved"><IMG SRC="data/pluto/level2/lor/jpeg/thumbnails/029912/lor_0299127173_0x630_sci_3.jpg" alt="Thumbnail image of " style="margin-bottom:6px;"></A><span style="font-weight:bold;"><p>2015-07-13</p><p>21:00:54 UTC</p></span><p>Exp: 150 msec</p><p>Target: PLUTO</p><p>Range: 0.7M km</p></div>
|
||||
|
||||
# Image URL
|
||||
#http://pluto.jhuapl.edu/soc/Pluto-Encounter/data/pluto/level2/lor/jpeg/029912/lor_0299124574_0x630_sci_4.jpg
|
||||
|
||||
for p in imagePositions:
|
||||
try:
|
||||
beginOffset = len('"><A HREF="view_obs.php?image=029912/')
|
||||
imageLimiterEnd = '&utc_time='
|
||||
imageEnd = source[p:].find(imageLimiterEnd)
|
||||
# imageLength = len('lor_0299127173_0x630_sci_3.jpg')
|
||||
utcDateOffset = len('&utc_time=')
|
||||
utcDateLength = len('2015-07-13')
|
||||
utcTimeOffset = len('<br>')
|
||||
utcTimeLength = len('21:00:54')
|
||||
# targetOffset = len(' UTC&description=&')
|
||||
targetLimiterBegin = '&target='
|
||||
targetLimiterEnd = '&range='
|
||||
targetBegin = source[p:].find(targetLimiterBegin)
|
||||
targetEnd = source[p:].find(targetLimiterEnd)
|
||||
|
||||
pos = p+beginOffset
|
||||
imageName = source[pos:p+imageEnd]
|
||||
imageLength = len('imageName')
|
||||
pos = p + imageEnd + utcDateOffset
|
||||
|
||||
utcDate = source[pos:pos+utcDateLength]
|
||||
pos = pos + utcDateLength + utcTimeOffset
|
||||
|
||||
utcTime = source[pos:pos+utcTimeLength]
|
||||
|
||||
target = source[p+targetBegin+len(targetLimiterBegin):p+targetEnd]
|
||||
|
||||
|
||||
urlFirstPart = imageName[len('lor_'): len('lor_') + len('029912')]
|
||||
imageURL = 'http://pluto.jhuapl.edu/soc/Pluto-Encounter/data/pluto/level2/lor/jpeg/' + urlFirstPart + '/' + imageName
|
||||
|
||||
print("ImageName: " + imageName)
|
||||
print("UTCDate: " + utcDate)
|
||||
print("UTCTime: " + utcTime)
|
||||
print("Target: " + target)
|
||||
print("URL: " + imageURL)
|
||||
|
||||
# Download image
|
||||
urlretrieve(imageURL, imageName)
|
||||
|
||||
# Create Label file
|
||||
with open(imageName[:-3] + 'lbl', 'w') as f:
|
||||
f.write('MISSION_NAME = "NEW HORIZONS"\n')
|
||||
f.write('SEQUENCE_ID = "UNUSED"\n')
|
||||
f.write('TARGET_NAME = "' + target + '"\n')
|
||||
f.write('START_TIME = ' + utcDate + 'T' + utcTime + '.000\n')
|
||||
f.write('STOP_TIME = ' + utcDate + 'T' + utcTime + '.005\n')
|
||||
f.write('INSTRUMENT_HOST_NAME = "NEW HORIZONS"\n')
|
||||
f.write('INSTRUMENT_ID = "LORRI"\n')
|
||||
f.write('DETECTOR_ID = "LORRI"\n')
|
||||
f.write('DETECTOR_TYPE = "CCD"\n')
|
||||
f.write('END\n')
|
||||
except Exception as inst:
|
||||
print('FAIL')
|
||||
print(inst)
|
||||
print(imageName)
|
||||
print(utcDate)
|
||||
print(utcTime)
|
||||
print(target)
|
||||
print(imageURL)
|
||||
|
||||
|
||||
for i in range(0,165):
|
||||
print("Downloading Page: " + str(i))
|
||||
downloadPage(i)
|
||||
@@ -141,7 +141,9 @@ std::shared_ptr<DownloadManager::FileFuture> DownloadManager::downloadFile(
|
||||
return nullptr;
|
||||
|
||||
std::shared_ptr<FileFuture> future = std::make_shared<FileFuture>(file.filename());
|
||||
FILE* fp = fopen(file.path().c_str(), "wb");
|
||||
errno = 0;
|
||||
FILE* fp = fopen(file.path().c_str(), "wb"); // write binary
|
||||
ghoul_assert(fp != nullptr, "Could not open/create file:\n" << file.path().c_str() << " \nerrno: " << errno);
|
||||
|
||||
LDEBUG("Start downloading file: '" << url << "' into file '" << file.path() << "'");
|
||||
|
||||
|
||||
+4
-2
@@ -34,10 +34,12 @@
|
||||
//#include <test_spicemanager.inl>
|
||||
//#include <test_scenegraphloader.inl>
|
||||
//#include <test_chunknode.inl>
|
||||
//#include <test_lrucache.inl>
|
||||
//#include <test_twmstileprovider.inl>
|
||||
#include <test_lrucache.inl>
|
||||
#include <test_twmstileprovider.inl>
|
||||
|
||||
//#include <test_luaconversions.inl>
|
||||
//#include <test_powerscalecoordinates.inl>
|
||||
//#include <test_angle.inl>
|
||||
//#include <test_latlonpatch.inl>
|
||||
//#include <test_texturetileset.inl>
|
||||
#include <test_gdalwms.inl>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2016 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
#include <openspace/../modules/globebrowsing/datastructures/angle.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
using namespace openspace;
|
||||
|
||||
class AngleTest : public testing::Test {};
|
||||
|
||||
TEST_F(AngleTest, DoubleConversions) {
|
||||
|
||||
ASSERT_EQ(dAngle::fromRadians(0).asDegrees(), 0) << "from radians to degrees";
|
||||
ASSERT_EQ(dAngle::HALF.asDegrees(), 180) << "from radians to degrees";
|
||||
ASSERT_EQ(dAngle::fromDegrees(180).asRadians(), dAngle::PI) << "from degrees to radians";
|
||||
|
||||
}
|
||||
|
||||
TEST_F(AngleTest, FloatConversions) {
|
||||
|
||||
ASSERT_EQ(fAngle::ZERO.asDegrees(), 0.0) << "from radians to degrees";
|
||||
ASSERT_EQ(fAngle::HALF.asDegrees(), 180.0) << "from radians to degrees";
|
||||
ASSERT_EQ(fAngle::fromDegrees(180).asRadians(), fAngle::PI) << "from degrees to radians";
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_F(AngleTest, Normalize) {
|
||||
|
||||
|
||||
ASSERT_NEAR(
|
||||
dAngle::fromDegrees(390).normalize().asDegrees(),
|
||||
30.0,
|
||||
dAngle::EPSILON
|
||||
) << "normalize to [0, 360]";
|
||||
|
||||
|
||||
dAngle a = dAngle::fromDegrees(190);
|
||||
a.normalizeAround(dAngle::ZERO);
|
||||
ASSERT_NEAR(
|
||||
a.asDegrees(),
|
||||
-170,
|
||||
dAngle::EPSILON
|
||||
) << "normalize to [-180,180]";
|
||||
|
||||
|
||||
dAngle b = dAngle::fromDegrees(190);
|
||||
b.normalizeAround(dAngle::fromDegrees(90));
|
||||
ASSERT_NEAR(
|
||||
b.asDegrees(),
|
||||
190,
|
||||
dAngle::EPSILON
|
||||
) << "normalize to [-90,270]";
|
||||
|
||||
|
||||
dAngle c = dAngle::fromDegrees(360);
|
||||
c.normalizeAround(dAngle::fromDegrees(1083.2));
|
||||
ASSERT_NEAR(
|
||||
c.asDegrees(),
|
||||
1080,
|
||||
dAngle::EPSILON
|
||||
) << "normalize to [903.2, 1263.2]";
|
||||
}
|
||||
|
||||
|
||||
TEST_F(AngleTest, Clamp) {
|
||||
|
||||
ASSERT_EQ(
|
||||
dAngle::fromDegrees(390).clamp(dAngle::ZERO, dAngle::HALF).asDegrees(),
|
||||
180,
|
||||
) << "clamp [0, 180]";
|
||||
|
||||
ASSERT_EQ(
|
||||
dAngle::fromDegrees(390).clamp(dAngle::ZERO, dAngle::FULL).asDegrees(),
|
||||
360,
|
||||
) << "clamp [0, 360]";
|
||||
}
|
||||
|
||||
|
||||
TEST_F(AngleTest, ConstClamp) {
|
||||
|
||||
const dAngle a = dAngle::fromDegrees(390);
|
||||
ASSERT_EQ(
|
||||
a.getClamped(dAngle::ZERO, dAngle::HALF).asDegrees(),
|
||||
180,
|
||||
) << "clamp [0, 180]";
|
||||
|
||||
const dAngle b = dAngle::fromDegrees(390);
|
||||
ASSERT_EQ(
|
||||
b.getClamped(dAngle::ZERO, dAngle::FULL).asDegrees(),
|
||||
360,
|
||||
) << "clamp [0, 360]";
|
||||
}
|
||||
@@ -39,3 +39,131 @@ TEST_F(LatLonPatchTest, findCenterControlPoint) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_F(LatLonPatchTest, TestFindClosestCorner) {
|
||||
|
||||
Scalar piOver4 = M_PI / 4;
|
||||
LatLon halfSize(piOver4, piOver4);
|
||||
LatLon center(0, 0);
|
||||
LatLonPatch patch(center, halfSize);
|
||||
|
||||
Scalar piOver3 = M_PI / 3;
|
||||
LatLon point(piOver3, piOver3);
|
||||
|
||||
LatLon closestCorner = patch.closestCorner(point);
|
||||
LatLon northEastCorner = patch.northEastCorner();
|
||||
|
||||
ASSERT_EQ(closestCorner.lat, northEastCorner.lat);
|
||||
ASSERT_EQ(closestCorner.lon, northEastCorner.lon);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(LatLonPatchTest, TestFindClosestCorner2) {
|
||||
|
||||
Scalar piOver6 = M_PI / 4;
|
||||
Scalar piOver3 = M_PI / 3;
|
||||
|
||||
LatLon halfSize(1.1*piOver6, 1.1*piOver6);
|
||||
LatLon center(piOver6, piOver6);
|
||||
LatLonPatch patch(center, halfSize);
|
||||
|
||||
LatLon point(0, 0);
|
||||
|
||||
LatLon closestCorner = patch.closestCorner(point);
|
||||
LatLon expectedCorner = patch.southWestCorner();
|
||||
|
||||
ASSERT_EQ(closestCorner.lat, expectedCorner.lat);
|
||||
ASSERT_EQ(closestCorner.lon, expectedCorner.lon);
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_F(LatLonPatchTest, TestSphericalClamp1) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
// inside patch latitude-wise, east of patch longitude-wise
|
||||
LatLon point(M_PI / 6, M_PI - 0.01);
|
||||
|
||||
|
||||
LatLon clampedPoint = patch.closestPoint(point);
|
||||
LatLon neCorner = patch.northEastCorner();
|
||||
ASSERT_EQ(clampedPoint.lat, neCorner.lat);
|
||||
ASSERT_EQ(clampedPoint.lon, neCorner.lon);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(LatLonPatchTest, TestSphericalClamp2) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
// inside patch latitude-wise, west of patch longitude-wise
|
||||
LatLon point(M_PI / 6, M_PI + 0.01);
|
||||
|
||||
LatLon clampedPoint = patch.closestPoint(point);
|
||||
LatLon nwCorner = patch.northWestCorner();
|
||||
ASSERT_EQ(clampedPoint.lat, nwCorner.lat);
|
||||
ASSERT_EQ(clampedPoint.lon, nwCorner.lon);
|
||||
}
|
||||
|
||||
TEST_F(LatLonPatchTest, TestSphericalClamp3) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
// North east of patch
|
||||
LatLon point(M_PI / 3, M_PI - 0.01);
|
||||
|
||||
LatLon clampedPoint = patch.closestPoint(point);
|
||||
LatLon neCorner = patch.northEastCorner();
|
||||
ASSERT_EQ(clampedPoint.lat, neCorner.lat);
|
||||
ASSERT_EQ(clampedPoint.lon, neCorner.lon);
|
||||
}
|
||||
|
||||
TEST_F(LatLonPatchTest, TestSphericalClamp4) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
// South east of patch
|
||||
LatLon point(-M_PI / 3, M_PI - 0.01);
|
||||
|
||||
LatLon clampedPoint = patch.closestPoint(point);
|
||||
LatLon seCorner = patch.southEastCorner();
|
||||
ASSERT_EQ(clampedPoint.lat, seCorner.lat);
|
||||
ASSERT_EQ(clampedPoint.lon, seCorner.lon);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(LatLonPatchTest, TestSphericalClamp5) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
// South west of patch
|
||||
LatLon point(-M_PI / 3, 3*M_PI + 0.01);
|
||||
|
||||
LatLon clampedPoint = patch.closestPoint(point);
|
||||
LatLon swCorner = patch.southWestCorner();
|
||||
ASSERT_EQ(clampedPoint.lat, swCorner.lat);
|
||||
ASSERT_EQ(clampedPoint.lon, swCorner.lon);
|
||||
}
|
||||
|
||||
int radAsDeg(double rads) {
|
||||
return floorf(Angle<Scalar>::fromRadians(rads).asDegrees());
|
||||
}
|
||||
|
||||
TEST_F(LatLonPatchTest, PrintingSphericalClamp) {
|
||||
LatLonPatch patch(0, 0, M_PI / 4, M_PI / 4);
|
||||
|
||||
using Ang = Angle<Scalar>;
|
||||
Ang delta = Ang::fromDegrees(30);
|
||||
std::cout << "point lat, lon --> clamped lat, lon" << std::endl;
|
||||
for (Ang lat = Ang::fromDegrees(90); lat > -Ang::QUARTER; lat -= delta) {
|
||||
for (Ang lon = Ang::fromDegrees(180); lon > -Ang::HALF; lon -= delta) {
|
||||
|
||||
LatLon point(lat.asRadians(), lon.asRadians());
|
||||
LatLon clamped = patch.closestPoint(point);
|
||||
std::cout
|
||||
<< radAsDeg(point.lat) << ", "
|
||||
<< radAsDeg(point.lon) << " --> "
|
||||
<< radAsDeg(clamped.lat) << ", "
|
||||
<< radAsDeg(clamped.lon) << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,11 @@ using namespace openspace;
|
||||
|
||||
TEST_F(TWMSTileProviderTest, Simple) {
|
||||
|
||||
TwmsTileProvider tileProvider;
|
||||
|
||||
TwmsTileProvider* tileProvider = new TwmsTileProvider();
|
||||
|
||||
TileIndex tileIndex = { 0, 0, 0 };
|
||||
tileProvider.getTile(tileIndex);
|
||||
tileProvider->getTile(tileIndex);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(2s);
|
||||
|
||||
Reference in New Issue
Block a user