mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-03-13 17:09:05 -05:00
Merge branch 'feature/globebrowsing' of github.com:OpenSpace/OpenSpace-Development into feature/globebrowsing
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<GDAL_WMS>
|
||||
<Service name="TMS">
|
||||
<ServerUrl>http://dzw9r5p966egh.cloudfront.net/catalog/Mars_Viking_MDIM21_ClrMosaic_global_232m/1.0.0//default/default028mm/${z}/${y}/${x}.jpg</ServerUrl>
|
||||
</Service>
|
||||
<DataWindow>
|
||||
<UpperLeftX>-180.0</UpperLeftX>
|
||||
<UpperLeftY>90</UpperLeftY>
|
||||
<LowerRightX>180.0</LowerRightX>
|
||||
<LowerRightY>-90</LowerRightY>
|
||||
<TileLevel>8</TileLevel>
|
||||
<TileCountX>2</TileCountX>
|
||||
<TileCountY>1</TileCountY>
|
||||
<YOrigin>top</YOrigin>
|
||||
</DataWindow>
|
||||
<Projection>EPSG:4326</Projection>
|
||||
<BlockSizeX>256</BlockSizeX>
|
||||
<BlockSizeY>256</BlockSizeY>
|
||||
<BandsCount>3</BandsCount>
|
||||
</GDAL_WMS>
|
||||
@@ -63,6 +63,7 @@ set(HEADER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentqueue.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/statscollector.h
|
||||
|
||||
|
||||
)
|
||||
@@ -104,6 +105,7 @@ set(SOURCE_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/lrucache.inl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/concurrentjobmanager.inl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/threadpool.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/other/statscollector.cpp
|
||||
|
||||
|
||||
)
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
#include <ctime>
|
||||
|
||||
namespace {
|
||||
const std::string _loggerCat = "ChunkLodGlobe";
|
||||
}
|
||||
@@ -52,18 +54,21 @@ namespace openspace {
|
||||
const ChunkIndex ChunkedLodGlobe::LEFT_HEMISPHERE_INDEX = ChunkIndex(0, 0, 1);
|
||||
const ChunkIndex ChunkedLodGlobe::RIGHT_HEMISPHERE_INDEX = ChunkIndex(1, 0, 1);
|
||||
|
||||
const GeodeticPatch ChunkedLodGlobe::COVERAGE = GeodeticPatch(0, 0, 90, 180);
|
||||
|
||||
|
||||
ChunkedLodGlobe::ChunkedLodGlobe(
|
||||
const Ellipsoid& ellipsoid,
|
||||
size_t segmentsPerPatch,
|
||||
std::shared_ptr<TileProviderManager> tileProviderManager)
|
||||
: _ellipsoid(ellipsoid)
|
||||
, _leftRoot(new ChunkNode(Chunk(this, LEFT_HEMISPHERE_INDEX)))
|
||||
, _rightRoot(new ChunkNode(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
|
||||
, _leftRoot(std::make_unique<ChunkNode>(Chunk(this, LEFT_HEMISPHERE_INDEX)))
|
||||
, _rightRoot(std::make_unique<ChunkNode>(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
|
||||
, minSplitDepth(2)
|
||||
, maxSplitDepth(22)
|
||||
, _savedCamera(nullptr)
|
||||
, _tileProviderManager(tileProviderManager)
|
||||
, stats(StatsCollector(absPath("test_stats")))
|
||||
{
|
||||
|
||||
auto geometry = std::make_shared<SkirtedGrid>(
|
||||
@@ -76,13 +81,13 @@ namespace openspace {
|
||||
_chunkCullers.push_back(std::make_unique<HorizonCuller>());
|
||||
_chunkCullers.push_back(std::make_unique<FrustumCuller>(AABB3(vec3(-1, -1, 0), vec3(1, 1, 1e35))));
|
||||
|
||||
|
||||
|
||||
|
||||
_chunkEvaluatorByAvailableTiles = std::make_unique<EvaluateChunkLevelByAvailableTileData>();
|
||||
_chunkEvaluatorByProjectedArea = std::make_unique<EvaluateChunkLevelByProjectedArea>();
|
||||
_chunkEvaluatorByDistance = std::make_unique<EvaluateChunkLevelByDistance>();
|
||||
|
||||
_renderer = std::make_unique<ChunkRenderer>(geometry, tileProviderManager);
|
||||
|
||||
}
|
||||
|
||||
ChunkedLodGlobe::~ChunkedLodGlobe() {
|
||||
@@ -116,6 +121,16 @@ namespace openspace {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ChunkNode& ChunkedLodGlobe::findChunkNode(const Geodetic2 p) const {
|
||||
ghoul_assert(COVERAGE.contains(p), "Point must be in lat [-90, 90] and lon [-180, 180]");
|
||||
return p.lon < COVERAGE.center().lon ? _leftRoot->find(p) : _rightRoot->find(p);
|
||||
}
|
||||
|
||||
ChunkNode& ChunkedLodGlobe::findChunkNode(const Geodetic2 p) {
|
||||
ghoul_assert(COVERAGE.contains(p), "Point must be in lat [-90, 90] and lon [-180, 180]");
|
||||
return p.lon < COVERAGE.center().lon ? _leftRoot->find(p) : _rightRoot->find(p);
|
||||
}
|
||||
|
||||
int ChunkedLodGlobe::getDesiredLevel(const Chunk& chunk, const RenderData& renderData) const {
|
||||
int desiredLevel = 0;
|
||||
if (debugOptions.levelByProjAreaElseDistance) {
|
||||
@@ -135,9 +150,23 @@ namespace openspace {
|
||||
desiredLevel = glm::clamp(desiredLevel, minSplitDepth, maxSplitDepth);
|
||||
return desiredLevel;
|
||||
}
|
||||
|
||||
|
||||
void ChunkedLodGlobe::render(const RenderData& data){
|
||||
|
||||
stats.startNewRecord();
|
||||
|
||||
|
||||
int j2000s = Time::now().unsyncedJ2000Seconds();
|
||||
int lastJ2000s = stats.i.previous("time");
|
||||
if (j2000s == lastJ2000s) {
|
||||
stats.disable();
|
||||
}
|
||||
else {
|
||||
stats.enable();
|
||||
}
|
||||
|
||||
stats.i["time"] = j2000s;
|
||||
|
||||
minDistToCamera = INFINITY;
|
||||
|
||||
_leftRoot->updateChunkTree(data);
|
||||
@@ -151,10 +180,16 @@ namespace openspace {
|
||||
|
||||
// Render function
|
||||
std::function<void(const ChunkNode&)> renderJob = [this, &data, &mvp](const ChunkNode& chunkNode) {
|
||||
stats.i["chunks"]++;
|
||||
const Chunk& chunk = chunkNode.getChunk();
|
||||
if (chunkNode.isLeaf() && chunk.isVisible()) {
|
||||
_renderer->renderChunk(chunkNode.getChunk(), data);
|
||||
debugRenderChunk(chunk, mvp);
|
||||
if (chunkNode.isLeaf()){
|
||||
stats.i["chunks leafs"]++;
|
||||
if (chunk.isVisible()) {
|
||||
stats.i["rendered chunks"]++;
|
||||
double t0 = Time::now().unsyncedJ2000Seconds();
|
||||
_renderer->renderChunk(chunkNode.getChunk(), data);
|
||||
debugRenderChunk(chunk, mvp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
#include <modules/globebrowsing/chunk/chunkrenderer.h>
|
||||
|
||||
#include <modules/globebrowsing/tile/tileprovider.h>
|
||||
#include <modules/globebrowsing/other/statscollector.h>
|
||||
|
||||
|
||||
namespace ghoul {
|
||||
namespace opengl {
|
||||
@@ -72,6 +74,9 @@ namespace openspace {
|
||||
void render(const RenderData& data) override;
|
||||
void update(const UpdateData& data) override;
|
||||
|
||||
const ChunkNode& findChunkNode(const Geodetic2 location) const;
|
||||
ChunkNode& findChunkNode(const Geodetic2 location);
|
||||
|
||||
void setStateMatrix(const glm::dmat3& stateMatrix);
|
||||
|
||||
bool testIfCullable(const Chunk& chunk, const RenderData& renderData) const;
|
||||
@@ -112,11 +117,14 @@ namespace openspace {
|
||||
bool levelByProjAreaElseDistance = true;
|
||||
} debugOptions;
|
||||
|
||||
StatsCollector stats;
|
||||
|
||||
private:
|
||||
|
||||
void debugRenderChunk(const Chunk& chunk, const glm::dmat4& data) const;
|
||||
|
||||
static const GeodeticPatch COVERAGE;
|
||||
|
||||
// Covers all negative longitudes
|
||||
std::unique_ptr<ChunkNode> _leftRoot;
|
||||
|
||||
@@ -141,6 +149,7 @@ namespace openspace {
|
||||
std::shared_ptr<Camera> _savedCamera;
|
||||
|
||||
std::shared_ptr<TileProviderManager> _tileProviderManager;
|
||||
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -154,6 +154,40 @@ void ChunkNode::reverseBreadthFirst(const std::function<void(const ChunkNode&)>&
|
||||
}
|
||||
}
|
||||
|
||||
#define CHUNK_NODE_FIND(node, p) \
|
||||
while (!node->isLeaf()) { \
|
||||
const Geodetic2 center = node->_chunk.surfacePatch().center();\
|
||||
int index = 0;\
|
||||
if (center.lon < p.lon) {\
|
||||
++index;\
|
||||
}\
|
||||
if (p.lat < center.lat) {\
|
||||
++index;\
|
||||
++index;\
|
||||
}\
|
||||
node = &(node->getChild((Quad)index));\
|
||||
}
|
||||
|
||||
const ChunkNode& ChunkNode::find(const Geodetic2& location) const {
|
||||
const ChunkNode* node = this;
|
||||
CHUNK_NODE_FIND(node, location);
|
||||
return *node;
|
||||
}
|
||||
|
||||
ChunkNode& ChunkNode::find(const Geodetic2& location) {
|
||||
ChunkNode* node = this;
|
||||
CHUNK_NODE_FIND(node, location);
|
||||
return *node;
|
||||
}
|
||||
|
||||
const ChunkNode& ChunkNode::getChild(Quad quad) const {
|
||||
return *_children[quad];
|
||||
}
|
||||
|
||||
ChunkNode& ChunkNode::getChild(Quad quad) {
|
||||
return *_children[quad];
|
||||
}
|
||||
|
||||
void ChunkNode::split(int depth) {
|
||||
if (depth > 0 && isLeaf()) {
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
@@ -180,9 +214,6 @@ void ChunkNode::merge() {
|
||||
ghoul_assert(isLeaf(), "ChunkNode must be leaf after merge");
|
||||
}
|
||||
|
||||
const ChunkNode& ChunkNode::getChild(Quad quad) const {
|
||||
return *_children[quad];
|
||||
}
|
||||
|
||||
const Chunk& ChunkNode::getChunk() const {
|
||||
return _chunk;
|
||||
|
||||
@@ -65,8 +65,13 @@ public:
|
||||
void depthFirst(const std::function<void(const ChunkNode&)>& f) const;
|
||||
void breadthFirst(const std::function<void(const ChunkNode&)>& f) const;
|
||||
void reverseBreadthFirst(const std::function<void(const ChunkNode&)>& f) const;
|
||||
|
||||
const ChunkNode& find(const Geodetic2& location) const;
|
||||
ChunkNode& find(const Geodetic2& location);
|
||||
|
||||
const ChunkNode& getChild(Quad quad) const;
|
||||
ChunkNode& getChild(Quad quad);
|
||||
|
||||
const Chunk& getChunk() const;
|
||||
|
||||
bool updateChunkTree(const RenderData& data);
|
||||
|
||||
@@ -62,23 +62,18 @@ namespace openspace {
|
||||
: _tileProviderManager(tileProviderManager)
|
||||
, _grid(grid)
|
||||
{
|
||||
_globalRenderingShaderProvider = std::shared_ptr<LayeredTextureShaderProvider>
|
||||
(new LayeredTextureShaderProvider(
|
||||
_globalRenderingShaderProvider = std::make_shared<LayeredTextureShaderProvider>(
|
||||
"GlobalChunkedLodPatch",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_vs.glsl",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl"));
|
||||
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl");
|
||||
|
||||
_localRenderingShaderProvider = std::shared_ptr<LayeredTextureShaderProvider>
|
||||
(new LayeredTextureShaderProvider(
|
||||
_localRenderingShaderProvider = std::make_shared<LayeredTextureShaderProvider>(
|
||||
"LocalChunkedLodPatch",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_vs.glsl",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl"));
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl");
|
||||
|
||||
_globalProgramUniformHandler = std::shared_ptr<LayeredTextureShaderUniformIdHandler>
|
||||
(new LayeredTextureShaderUniformIdHandler());
|
||||
|
||||
_localProgramUniformHandler = std::shared_ptr<LayeredTextureShaderUniformIdHandler>
|
||||
(new LayeredTextureShaderUniformIdHandler());
|
||||
_globalProgramUniformHandler = std::make_shared<LayeredTextureShaderUniformIdHandler>();
|
||||
_localProgramUniformHandler = std::make_shared<LayeredTextureShaderUniformIdHandler>();
|
||||
|
||||
}
|
||||
|
||||
@@ -170,8 +165,7 @@ namespace openspace {
|
||||
LayeredTextures::NUM_TEXTURE_CATEGORIES> tileProviders;
|
||||
LayeredTexturePreprocessingData layeredTexturePreprocessingData;
|
||||
|
||||
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++)
|
||||
{
|
||||
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
|
||||
tileProviders[category] = _tileProviderManager->getActivatedLayerCategory(
|
||||
LayeredTextures::TextureCategory(category));
|
||||
|
||||
@@ -209,19 +203,16 @@ namespace openspace {
|
||||
ghoul::opengl::TextureUnit blendTexture1;
|
||||
ghoul::opengl::TextureUnit blendTexture2;
|
||||
};
|
||||
std::array<std::vector<BlendTexUnits>,
|
||||
LayeredTextures::NUM_TEXTURE_CATEGORIES> texUnits;
|
||||
std::array<std::vector<BlendTexUnits>, LayeredTextures::NUM_TEXTURE_CATEGORIES> texUnits;
|
||||
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
|
||||
texUnits[category].resize(tileProviders[category].size());
|
||||
}
|
||||
|
||||
// Go through all the categories
|
||||
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++)
|
||||
{
|
||||
for (size_t category = 0; category < LayeredTextures::NUM_TEXTURE_CATEGORIES; category++) {
|
||||
// Go through all the providers in this category
|
||||
int i = 0;
|
||||
for (auto it = tileProviders[category].begin(); it != tileProviders[category].end(); it++)
|
||||
{
|
||||
for (auto it = tileProviders[category].begin(); it != tileProviders[category].end(); it++) {
|
||||
auto tileProvider = it->get();
|
||||
|
||||
// Get the texture that should be used for rendering
|
||||
@@ -272,8 +263,9 @@ namespace openspace {
|
||||
|
||||
// Go through all the height maps and set depth tranforms
|
||||
int i = 0;
|
||||
for (auto it = tileProviders[LayeredTextures::HeightMaps].begin();
|
||||
it != tileProviders[LayeredTextures::HeightMaps].end(); it++) {
|
||||
auto it = tileProviders[LayeredTextures::HeightMaps].begin();
|
||||
auto end = tileProviders[LayeredTextures::HeightMaps].end();
|
||||
for (; it != end; it++) {
|
||||
auto tileProvider = *it;
|
||||
|
||||
TileDepthTransform depthTransform = tileProvider->depthTransform();
|
||||
@@ -314,7 +306,7 @@ namespace openspace {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(performAnyBlending) {
|
||||
if (performAnyBlending) {
|
||||
float distanceScaleFactor = chunk.owner()->lodScaleFactor * ellipsoid.minimumRadius();
|
||||
programObject->setUniform("cameraPosition", vec3(data.camera.positionVec3()));
|
||||
programObject->setUniform("distanceScaleFactor", distanceScaleFactor);
|
||||
|
||||
@@ -181,7 +181,12 @@ namespace openspace {
|
||||
return _center.lon + _halfSize.lon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GeodeticPatch::contains(const Geodetic2& p) const {
|
||||
Geodetic2 diff = _center - p;
|
||||
return glm::abs(diff.lat) <= _halfSize.lat && glm::abs(diff.lon) <= _halfSize.lon;
|
||||
}
|
||||
|
||||
|
||||
Scalar GeodeticPatch::edgeLatitudeNearestEquator() const {
|
||||
return _center.lat + _halfSize.lat * (isNorthern() ? -1 : 1);
|
||||
|
||||
@@ -127,6 +127,11 @@ public:
|
||||
Scalar minLon() const;
|
||||
Scalar maxLon() const;
|
||||
|
||||
/**
|
||||
* returns true if the specified coordinate is contained within the patch
|
||||
*/
|
||||
bool contains(const Geodetic2& p) const;
|
||||
|
||||
|
||||
/**
|
||||
* Clamps a point to the patch region
|
||||
|
||||
@@ -197,8 +197,8 @@ namespace openspace {
|
||||
|
||||
// Get the uv coordinates to sample from
|
||||
Geodetic2 geodeticPosition = _ellipsoid.cartesianToGeodetic2(position);
|
||||
int chunkLevel =
|
||||
tileProvider->getAsyncTileReader()->getTextureDataProvider()->getMaximumLevel();
|
||||
int chunkLevel = _chunkedLodGlobe->findChunkNode(geodeticPosition).getChunk().index().level;
|
||||
|
||||
ChunkIndex chunkIdx = ChunkIndex(geodeticPosition, chunkLevel);
|
||||
GeodeticPatch patch = GeodeticPatch(chunkIdx);
|
||||
Geodetic2 geoDiffPatch = patch.getCorner(Quad::NORTH_EAST) - patch.getCorner(Quad::SOUTH_WEST);
|
||||
|
||||
34
modules/globebrowsing/other/statscollector.cpp
Normal file
34
modules/globebrowsing/other/statscollector.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/other/statscollector.h>
|
||||
|
||||
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
262
modules/globebrowsing/other/statscollector.h
Normal file
262
modules/globebrowsing/other/statscollector.h
Normal file
@@ -0,0 +1,262 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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 __STATS_TRACKER_H__
|
||||
#define __STATS_TRACKER_H__
|
||||
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/filesystem/filesystem>
|
||||
|
||||
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct StatsRecord : public std::unordered_map<std::string, T> {
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct StatsCollection : public std::vector<StatsRecord<T>> {
|
||||
std::set<std::string> keys;
|
||||
};
|
||||
|
||||
|
||||
template <typename T> class TemplatedStatsCollector{
|
||||
public:
|
||||
|
||||
TemplatedStatsCollector(bool& enabled, const std::string& delimiter)
|
||||
: _enabled(enabled)
|
||||
, _delimiter(delimiter)
|
||||
, _writePos(0) { };
|
||||
|
||||
~TemplatedStatsCollector() { };
|
||||
|
||||
void startNewRecord() {
|
||||
if(_enabled)
|
||||
_data.push_back(StatsRecord<T>());
|
||||
}
|
||||
|
||||
T& operator[](const std::string& name) {
|
||||
if (_enabled) {
|
||||
_data.keys.insert(name);
|
||||
return _data.back()[name];
|
||||
}
|
||||
else return _dummy;
|
||||
}
|
||||
|
||||
T previous(const std::string& name) {
|
||||
if (_data.size() > 1) {
|
||||
return _data[_data.size() - 2][name];
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
bool hasHeaders() {
|
||||
return _data.keys.size() > 0;
|
||||
}
|
||||
|
||||
bool hasRecordsToWrite() {
|
||||
return _writePos < _data.size() - 1;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_data.clear();
|
||||
_writePos = 0;
|
||||
}
|
||||
|
||||
void writeHeader(std::ostream& os) {
|
||||
auto keyIt = _data.keys.begin();
|
||||
os << *keyIt;
|
||||
while (++keyIt != _data.keys.end()) {
|
||||
os << _delimiter << *keyIt;
|
||||
}
|
||||
}
|
||||
|
||||
void writeNextRecord(std::ostream& os) {
|
||||
if (hasRecordsToWrite()) {
|
||||
// output line by line
|
||||
StatsRecord<T>& record = _data[_writePos];
|
||||
|
||||
// Access every key. Records with no entry will get a default value
|
||||
auto keyIt = _data.keys.begin();
|
||||
if (keyIt != _data.keys.end()) {
|
||||
os << record[(*keyIt)];
|
||||
while (++keyIt != _data.keys.end()) {
|
||||
os << _delimiter << record[(*keyIt)];
|
||||
}
|
||||
}
|
||||
|
||||
_writePos++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
StatsCollection<T> _data;
|
||||
T _dummy; // used when disabled
|
||||
bool& _enabled;
|
||||
|
||||
size_t _writePos;
|
||||
std::string _delimiter;
|
||||
|
||||
};
|
||||
|
||||
class StatsCollector {
|
||||
|
||||
public:
|
||||
|
||||
StatsCollector() = delete;
|
||||
|
||||
StatsCollector(const std::string& filename, const std::string& delimiter = ",", bool enabled = true)
|
||||
: _filename(filename)
|
||||
, _enabled(enabled)
|
||||
, _delimiter(delimiter)
|
||||
, _hasWrittenHead(false)
|
||||
, i(TemplatedStatsCollector<int>(_enabled, delimiter))
|
||||
, d(TemplatedStatsCollector<double>(_enabled, delimiter))
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
~StatsCollector() {
|
||||
dumpToDisk();
|
||||
}
|
||||
|
||||
void startNewRecord() {
|
||||
if (_enabled) {
|
||||
i.startNewRecord();
|
||||
d.startNewRecord();
|
||||
}
|
||||
}
|
||||
|
||||
void disable() {
|
||||
_enabled = false;
|
||||
}
|
||||
|
||||
void enable() {
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
void dumpToDisk() {
|
||||
if (!_hasWrittenHead) {
|
||||
writeHead();
|
||||
}
|
||||
writeData();
|
||||
}
|
||||
|
||||
TemplatedStatsCollector<int> i;
|
||||
TemplatedStatsCollector<double> d;
|
||||
|
||||
private:
|
||||
void writeHead() {
|
||||
std::ofstream ofs(_filename);
|
||||
if (i.hasHeaders()) {
|
||||
i.writeHeader(ofs);
|
||||
if (d.hasHeaders()) {
|
||||
ofs << _delimiter;
|
||||
d.writeHeader(ofs);
|
||||
}
|
||||
}
|
||||
else {
|
||||
d.writeHeader(ofs);
|
||||
}
|
||||
ofs << std::endl;
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
void writeData() {
|
||||
std::ofstream ofs(_filename, std::ofstream::out | std::ofstream::app);
|
||||
while (i.hasRecordsToWrite() || d.hasRecordsToWrite()) {
|
||||
if (i.hasHeaders() && d.hasHeaders()) {
|
||||
i.writeNextRecord(ofs); ofs << _delimiter; d.writeNextRecord(ofs);
|
||||
}
|
||||
else {
|
||||
i.writeNextRecord(ofs); d.writeNextRecord(ofs);
|
||||
}
|
||||
ofs << std::endl;
|
||||
}
|
||||
i.reset(); d.reset();
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
std::string _filename;
|
||||
std::string _delimiter;
|
||||
bool _enabled;
|
||||
bool _hasWrittenHead;
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
template <typename T>
|
||||
struct StatsCsvWriter {
|
||||
StatsCsvWriter(const std::string& delimiter)
|
||||
: _delimiter(delimiter) { };
|
||||
|
||||
virtual void write(const StatsCollection<T>& stats, const std::string& filename) {
|
||||
std::ofstream ofs(filename);
|
||||
|
||||
// output headers
|
||||
auto keyIt = stats.keys.begin();
|
||||
ofs << *keyIt;
|
||||
while (keyIt++ != stats.keys.end()) {
|
||||
ofs << _delimiter << *keyIt;
|
||||
}
|
||||
ofs << std::endl;
|
||||
|
||||
// output line by line
|
||||
for (const StatsRecord<T>& record : stats) {
|
||||
// Access every key. Records with no entry will get a default value
|
||||
auto keyIt = stats.keys.begin();
|
||||
ofs << record[*keyIt];
|
||||
while (keyIt++ != stats.keys.end()) {
|
||||
ofs << _delimiter << record[*keyIt];
|
||||
}
|
||||
ofs << std::endl;
|
||||
}
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _delimiter;
|
||||
};
|
||||
*/
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // __STATS_TRACKER_H__
|
||||
114
monitor/index.html
Normal file
114
monitor/index.html
Normal file
@@ -0,0 +1,114 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="http://getbootstrap.com/examples/justified-nav/justified-nav.css" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<svg></svg>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function doVis(){
|
||||
var filename = "../data/scene/debugglobe/test_stats";
|
||||
|
||||
var margin = {top: 20, right: 80, bottom: 30, left: 50},
|
||||
width = 960 - margin.left - margin.right,
|
||||
height = 500 - margin.top - margin.bottom;
|
||||
|
||||
var parseDate = d3.time.format("%Y%m").parse;
|
||||
|
||||
var x = d3.time.scale()
|
||||
.range([0, width]);
|
||||
|
||||
var y = d3.scale.linear()
|
||||
.range([height, 0]);
|
||||
|
||||
var color = d3.scale.category10();
|
||||
|
||||
var xAxis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.orient("bottom");
|
||||
|
||||
var yAxis = d3.svg.axis()
|
||||
.scale(y)
|
||||
.orient("left");
|
||||
|
||||
var line = d3.svg.line()
|
||||
//.interpolate("basis")
|
||||
.x(function(d) { return x(d.time); })
|
||||
.y(function(d) { return y(d.value); });
|
||||
|
||||
d3.select("svg").remove();
|
||||
var svg = d3.select("body").append('svg')
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
d3.csv(filename, function(error, data) {
|
||||
if (error) throw error;
|
||||
|
||||
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "time"; }));
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.time = new Date(+d.time);
|
||||
});
|
||||
|
||||
var cities = color.domain().map(function(name) {
|
||||
return {
|
||||
name: name,
|
||||
values: data.map(function(d) {
|
||||
return {time: d.time, value: +d[name]};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
x.domain(d3.extent(data, function(d) { return d.time; }));
|
||||
|
||||
y.domain([
|
||||
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.value; }); }),
|
||||
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.value; }); })
|
||||
]);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(xAxis);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis)
|
||||
.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", 6)
|
||||
.attr("dy", ".71em")
|
||||
.style("text-anchor", "end")
|
||||
.text("");
|
||||
|
||||
var city = svg.selectAll(".city")
|
||||
.data(cities)
|
||||
.enter().append("g")
|
||||
.attr("class", "city");
|
||||
|
||||
city.append("path")
|
||||
.attr("class", "line")
|
||||
.attr("d", function(d) { return line(d.values); })
|
||||
.style("stroke", function(d) { return color(d.name); });
|
||||
|
||||
city.append("text")
|
||||
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
|
||||
.attr("transform", function(d) { return "translate(" + x(d.value.time) + "," + y(d.value.value) + ")"; })
|
||||
.attr("x", 3)
|
||||
.attr("dy", ".35em")
|
||||
.text(function(d) { return d.name; });
|
||||
});
|
||||
}
|
||||
doVis();
|
||||
//setInterval(doVis, 1000);
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
20
monitor/style.css
Normal file
20
monitor/style.css
Normal file
@@ -0,0 +1,20 @@
|
||||
body {
|
||||
font: 10px sans-serif;
|
||||
}
|
||||
|
||||
.axis path,
|
||||
.axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.x.axis path {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.line {
|
||||
fill: none;
|
||||
stroke: steelblue;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
Reference in New Issue
Block a user