diff --git a/data/scene/debugglobe/map_service_configs/MARS_Viking_MDIM21.xml b/data/scene/debugglobe/map_service_configs/MARS_Viking_MDIM21.xml
new file mode 100644
index 0000000000..cd87af69be
--- /dev/null
+++ b/data/scene/debugglobe/map_service_configs/MARS_Viking_MDIM21.xml
@@ -0,0 +1,19 @@
+
+
+ http://dzw9r5p966egh.cloudfront.net/catalog/Mars_Viking_MDIM21_ClrMosaic_global_232m/1.0.0//default/default028mm/${z}/${y}/${x}.jpg
+
+
+ -180.0
+ 90
+ 180.0
+ -90
+ 8
+ 2
+ 1
+ top
+
+ EPSG:4326
+ 256
+ 256
+ 3
+
\ No newline at end of file
diff --git a/modules/globebrowsing/CMakeLists.txt b/modules/globebrowsing/CMakeLists.txt
index 1c032afb0b..fe23fb01da 100644
--- a/modules/globebrowsing/CMakeLists.txt
+++ b/modules/globebrowsing/CMakeLists.txt
@@ -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
)
diff --git a/modules/globebrowsing/chunk/chunkedlodglobe.cpp b/modules/globebrowsing/chunk/chunkedlodglobe.cpp
index fb26a85320..37f6203340 100644
--- a/modules/globebrowsing/chunk/chunkedlodglobe.cpp
+++ b/modules/globebrowsing/chunk/chunkedlodglobe.cpp
@@ -43,6 +43,8 @@
#define _USE_MATH_DEFINES
#include
+#include
+
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)
: _ellipsoid(ellipsoid)
- , _leftRoot(new ChunkNode(Chunk(this, LEFT_HEMISPHERE_INDEX)))
- , _rightRoot(new ChunkNode(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
+ , _leftRoot(std::make_unique(Chunk(this, LEFT_HEMISPHERE_INDEX)))
+ , _rightRoot(std::make_unique(Chunk(this, RIGHT_HEMISPHERE_INDEX)))
, minSplitDepth(2)
, maxSplitDepth(22)
, _savedCamera(nullptr)
, _tileProviderManager(tileProviderManager)
+ , stats(StatsCollector(absPath("test_stats")))
{
auto geometry = std::make_shared(
@@ -76,13 +81,13 @@ namespace openspace {
_chunkCullers.push_back(std::make_unique());
_chunkCullers.push_back(std::make_unique(AABB3(vec3(-1, -1, 0), vec3(1, 1, 1e35))));
-
-
+
_chunkEvaluatorByAvailableTiles = std::make_unique();
_chunkEvaluatorByProjectedArea = std::make_unique();
_chunkEvaluatorByDistance = std::make_unique();
_renderer = std::make_unique(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 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);
+ }
}
};
diff --git a/modules/globebrowsing/chunk/chunkedlodglobe.h b/modules/globebrowsing/chunk/chunkedlodglobe.h
index 4a985effcb..56e8d2e39c 100644
--- a/modules/globebrowsing/chunk/chunkedlodglobe.h
+++ b/modules/globebrowsing/chunk/chunkedlodglobe.h
@@ -44,6 +44,8 @@
#include
#include
+#include
+
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 _leftRoot;
@@ -141,6 +149,7 @@ namespace openspace {
std::shared_ptr _savedCamera;
std::shared_ptr _tileProviderManager;
+
};
} // namespace openspace
diff --git a/modules/globebrowsing/chunk/chunknode.cpp b/modules/globebrowsing/chunk/chunknode.cpp
index 2efda5a548..2198fc4276 100644
--- a/modules/globebrowsing/chunk/chunknode.cpp
+++ b/modules/globebrowsing/chunk/chunknode.cpp
@@ -154,6 +154,40 @@ void ChunkNode::reverseBreadthFirst(const std::function&
}
}
+#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;
diff --git a/modules/globebrowsing/chunk/chunknode.h b/modules/globebrowsing/chunk/chunknode.h
index 8ee054ba2b..3968559cee 100644
--- a/modules/globebrowsing/chunk/chunknode.h
+++ b/modules/globebrowsing/chunk/chunknode.h
@@ -65,8 +65,13 @@ public:
void depthFirst(const std::function& f) const;
void breadthFirst(const std::function& f) const;
void reverseBreadthFirst(const std::function& 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);
diff --git a/modules/globebrowsing/chunk/chunkrenderer.cpp b/modules/globebrowsing/chunk/chunkrenderer.cpp
index eac705c079..0081659cd6 100644
--- a/modules/globebrowsing/chunk/chunkrenderer.cpp
+++ b/modules/globebrowsing/chunk/chunkrenderer.cpp
@@ -62,23 +62,18 @@ namespace openspace {
: _tileProviderManager(tileProviderManager)
, _grid(grid)
{
- _globalRenderingShaderProvider = std::shared_ptr
- (new LayeredTextureShaderProvider(
+ _globalRenderingShaderProvider = std::make_shared(
"GlobalChunkedLodPatch",
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_vs.glsl",
- "${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl"));
+ "${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl");
- _localRenderingShaderProvider = std::shared_ptr
- (new LayeredTextureShaderProvider(
+ _localRenderingShaderProvider = std::make_shared(
"LocalChunkedLodPatch",
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_vs.glsl",
- "${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl"));
+ "${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl");
- _globalProgramUniformHandler = std::shared_ptr
- (new LayeredTextureShaderUniformIdHandler());
-
- _localProgramUniformHandler = std::shared_ptr
- (new LayeredTextureShaderUniformIdHandler());
+ _globalProgramUniformHandler = std::make_shared();
+ _localProgramUniformHandler = std::make_shared();
}
@@ -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,
- LayeredTextures::NUM_TEXTURE_CATEGORIES> texUnits;
+ std::array, 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);
diff --git a/modules/globebrowsing/geometry/geodetic2.cpp b/modules/globebrowsing/geometry/geodetic2.cpp
index 5ff5601887..66279858eb 100644
--- a/modules/globebrowsing/geometry/geodetic2.cpp
+++ b/modules/globebrowsing/geometry/geodetic2.cpp
@@ -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);
diff --git a/modules/globebrowsing/geometry/geodetic2.h b/modules/globebrowsing/geometry/geodetic2.h
index 786970aeb7..5286ed8995 100644
--- a/modules/globebrowsing/geometry/geodetic2.h
+++ b/modules/globebrowsing/geometry/geodetic2.h
@@ -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
diff --git a/modules/globebrowsing/globes/renderableglobe.cpp b/modules/globebrowsing/globes/renderableglobe.cpp
index beed4d8794..5042f32153 100644
--- a/modules/globebrowsing/globes/renderableglobe.cpp
+++ b/modules/globebrowsing/globes/renderableglobe.cpp
@@ -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);
diff --git a/modules/globebrowsing/other/statscollector.cpp b/modules/globebrowsing/other/statscollector.cpp
new file mode 100644
index 0000000000..62ee4f6c89
--- /dev/null
+++ b/modules/globebrowsing/other/statscollector.cpp
@@ -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
+
+
+
+namespace openspace {
+
+
+} // namespace openspace
\ No newline at end of file
diff --git a/modules/globebrowsing/other/statscollector.h b/modules/globebrowsing/other/statscollector.h
new file mode 100644
index 0000000000..3caec5b8b2
--- /dev/null
+++ b/modules/globebrowsing/other/statscollector.h
@@ -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
+#include
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+
+namespace openspace {
+
+
+ template
+ struct StatsRecord : public std::unordered_map {
+
+ };
+
+ template
+ struct StatsCollection : public std::vector> {
+ std::set keys;
+ };
+
+
+ template 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& 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& 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 _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(_enabled, delimiter))
+ , d(TemplatedStatsCollector(_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 i;
+ TemplatedStatsCollector 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
+ struct StatsCsvWriter {
+ StatsCsvWriter(const std::string& delimiter)
+ : _delimiter(delimiter) { };
+
+ virtual void write(const StatsCollection& 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& 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__
\ No newline at end of file
diff --git a/monitor/index.html b/monitor/index.html
new file mode 100644
index 0000000000..e5c69edb16
--- /dev/null
+++ b/monitor/index.html
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/monitor/style.css b/monitor/style.css
new file mode 100644
index 0000000000..ebf2021fb5
--- /dev/null
+++ b/monitor/style.css
@@ -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;
+}