mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-05 11:09:37 -06:00
Pr/trails (#170)
* Implement new RenderableTrails as abstract base class - Implement RenderableTrailsOrbit and RenderableTrailsTrajectory as concrete instances Remove old RenderableTrails and RenderableTrailsNew classes Adapt mod files to the new structure * Addressed Pull Request comments
This commit is contained in:
455
modules/base/rendering/renderabletrailorbit.cpp
Normal file
455
modules/base/rendering/renderabletrailorbit.cpp
Normal file
@@ -0,0 +1,455 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/base/rendering/renderabletrailorbit.h>
|
||||
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/scene/translation.h>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
// This class is using a VBO ring buffer + a constantly updated point as follows:
|
||||
// Structure of the array with a _resolution of 16. FF denotes the floating position that
|
||||
// is updated every frame:
|
||||
// ---------------------------------------------------------------------------------
|
||||
// | FF | | | | | | | | | | | | | | | |
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
// <------ newer in time oldest
|
||||
//
|
||||
// In the begining the floating value starts at 0; this means that array element 0 is
|
||||
// updated and uploaded to the GPU at every frame. The FF+1 element is the newest fixed
|
||||
// location and FF-1 element is the oldest fixed location (including wrapping around the
|
||||
// array) with the times of _lastPointTime and _firstPointTime.
|
||||
//
|
||||
// If the time progresses forwards and abs(time - _lastPointTime) becomes big enough, the
|
||||
// oldest point is removed and a new fixed location is added. In the ring buffer this
|
||||
// would be represented as:
|
||||
// ---------------------------------------------------------------------------------
|
||||
// | | | | | | | | | | | | | | | | FF |
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
// <------ newer in time oldest
|
||||
//
|
||||
// Thus making the floating point traverse backwards through the array and element 0 being
|
||||
// the newest fixed point. If the time processes backwards, the floating point moves
|
||||
// towards the upper areas of the array instead.
|
||||
// In both cases, only the values that have been changed will be uploaded to the GPU.
|
||||
//
|
||||
// For the rendering, this is achieved by using an index buffer that is twice the size of
|
||||
// the vertex buffer containing identical two sequences indexing the vertex array.
|
||||
// In an example of size 8:
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||
//
|
||||
// The rendering step needs to know only the offset into the array (denoted by FF as the
|
||||
// floating position above) and use the index array from the position. Since the indices
|
||||
// in this array wrap around, so will the rendering of the vertices. Example:
|
||||
// FF := 10
|
||||
// Rendering 16 elements will 'generate' the index buffer:
|
||||
// 10 11 12 13 14 15 00 01 02 03 04 05 06 07 08 09
|
||||
//
|
||||
//
|
||||
// NB: This method was implemented without a ring buffer before by manually shifting the
|
||||
// items in memory as was shown to be much slower than the current system. ---abock
|
||||
|
||||
namespace {
|
||||
const char* KeyPeriod = "Period";
|
||||
const char* KeyResolution = "Resolution";
|
||||
}
|
||||
|
||||
namespace openspace {
|
||||
|
||||
openspace::Documentation RenderableTrailOrbit::Documentation() {
|
||||
using namespace documentation;
|
||||
openspace::Documentation doc{
|
||||
"RenderableTrailOrbit",
|
||||
"base_renderable_renderabletrailorbit",
|
||||
{
|
||||
{
|
||||
"Type",
|
||||
new StringEqualVerifier("RenderableTrailOrbit"),
|
||||
"",
|
||||
Optional::No
|
||||
},
|
||||
{
|
||||
KeyPeriod,
|
||||
new DoubleVerifier,
|
||||
"The objects period, i.e. the length of its orbit around the parent "
|
||||
"object given in (Earth) days. In the case of Earth, this would be a "
|
||||
"sidereal year (=365.242 days). If this values is specified as multiples "
|
||||
"of the period, it is possible to show the effects of precession.",
|
||||
Optional::No
|
||||
},
|
||||
{
|
||||
KeyResolution,
|
||||
new IntVerifier,
|
||||
"The number of samples along the orbit. This determines the resolution "
|
||||
"of the trail; the tradeoff being that a higher resolution is able to "
|
||||
"resolve more detail, but will take more resources while rendering, too. "
|
||||
"The higher, the smoother the trail, but also more memory will be used.",
|
||||
Optional::No
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Insert the parents documentation entries until we have a verifier that can deal
|
||||
// with class hierarchy
|
||||
openspace::Documentation parentDoc = RenderableTrail::Documentation();
|
||||
doc.entries.insert(
|
||||
doc.entries.end(),
|
||||
parentDoc.entries.begin(),
|
||||
parentDoc.entries.end()
|
||||
);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
RenderableTrailOrbit::RenderableTrailOrbit(const ghoul::Dictionary& dictionary)
|
||||
: RenderableTrail(dictionary)
|
||||
, _period("period", "Period in days", 0.0, 0.0, 1e9)
|
||||
, _resolution("resoluion", "Number of Samples along Orbit", 10000, 1, 1e6)
|
||||
, _needsFullSweep(true)
|
||||
, _indexBufferDirty(true)
|
||||
{
|
||||
documentation::testSpecificationAndThrow(
|
||||
Documentation(),
|
||||
dictionary,
|
||||
"RenderableTrailOrbit"
|
||||
);
|
||||
|
||||
// Period is in days
|
||||
using namespace std::chrono;
|
||||
int factor = duration_cast<seconds>(hours(24)).count();
|
||||
_period = dictionary.value<double>(KeyPeriod) * factor;
|
||||
_period.onChange([&] { _needsFullSweep = true; _indexBufferDirty = true; });
|
||||
addProperty(_period);
|
||||
|
||||
_resolution = static_cast<int>(dictionary.value<double>(KeyResolution));
|
||||
_resolution.onChange([&] { _needsFullSweep = true; _indexBufferDirty = true; });
|
||||
addProperty(_resolution);
|
||||
|
||||
// We store the vertices with (excluding the wrapping) decending temporal order
|
||||
_primaryRenderInformation.sorting = RenderInformation::VertexSorting::NewestFirst;
|
||||
}
|
||||
|
||||
bool RenderableTrailOrbit::initialize() {
|
||||
bool res = RenderableTrail::initialize();
|
||||
|
||||
glGenVertexArrays(1, &_primaryRenderInformation._vaoID);
|
||||
glGenBuffers(1, &_primaryRenderInformation._vBufferID);
|
||||
glGenBuffers(1, &_primaryRenderInformation._iBufferID);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool RenderableTrailOrbit::deinitialize() {
|
||||
glDeleteVertexArrays(1, &_primaryRenderInformation._vaoID);
|
||||
glDeleteBuffers(1, &_primaryRenderInformation._vBufferID);
|
||||
glDeleteBuffers(1, &_primaryRenderInformation._iBufferID);
|
||||
|
||||
return RenderableTrail::deinitialize();
|
||||
}
|
||||
|
||||
void RenderableTrailOrbit::update(const UpdateData& data) {
|
||||
// Overview:
|
||||
// 1. Update trails
|
||||
// 2. Update floating position
|
||||
// 3. Determine which parts of the array to upload and upload the data
|
||||
|
||||
// Early bailout when we don't move in time
|
||||
if (data.timePaused || data.delta == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 1
|
||||
// Update the trails; the report contains whether any of the other values has been
|
||||
// touched and if so, how many
|
||||
UpdateReport report = updateTrails(data);
|
||||
|
||||
// 2
|
||||
// Write the current location into the floating position
|
||||
glm::vec3 p = _translation->position(data.time);
|
||||
_vertexArray[_primaryRenderInformation.first] = { p.x, p.y, p.z };
|
||||
|
||||
glBindVertexArray(_primaryRenderInformation._vaoID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _primaryRenderInformation._vBufferID);
|
||||
|
||||
// 3
|
||||
if (!report.needsUpdate) {
|
||||
// If no other values have been touched, we only need to upload the floating value
|
||||
glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
_primaryRenderInformation.first * sizeof(TrailVBOLayout),
|
||||
sizeof(TrailVBOLayout),
|
||||
_vertexArray.data() + _primaryRenderInformation.first
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Otherwise we need to check how many values have been changed
|
||||
if (report.nUpdated == UpdateReport::All) {
|
||||
// If all of the values have been invalidated, we need to upload the entire
|
||||
// array
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
_vertexArray.size() * sizeof(TrailVBOLayout),
|
||||
_vertexArray.data(),
|
||||
GL_STREAM_DRAW
|
||||
);
|
||||
|
||||
if (_indexBufferDirty) {
|
||||
// We only need to upload the index buffer if it has been invalidated
|
||||
// by changing the number of values we want to represent
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _primaryRenderInformation._iBufferID);
|
||||
glBufferData(
|
||||
GL_ELEMENT_ARRAY_BUFFER,
|
||||
_indexArray.size() * sizeof(unsigned int),
|
||||
_indexArray.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
_indexBufferDirty = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The lambda expression that will upload parts of the array starting at
|
||||
// begin and containing length number of elements
|
||||
auto upload = [this](int begin, int length) {
|
||||
glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
begin * sizeof(TrailVBOLayout),
|
||||
sizeof(TrailVBOLayout) * length,
|
||||
_vertexArray.data() + begin
|
||||
);
|
||||
};
|
||||
|
||||
// Only update the changed ones
|
||||
// Since we are using a ring buffer, the number of updated needed might be
|
||||
// bigger than our current points, which means we have to split the upload
|
||||
// into two calls.
|
||||
if (report.nUpdated > 0) {
|
||||
// deltaT is positive, so the pointer is moving backwards and update has
|
||||
// to happen towards the front
|
||||
|
||||
// Starting index
|
||||
int i = _primaryRenderInformation.first;
|
||||
// Number of values
|
||||
int n = report.nUpdated + 1; // +1 for the floating position
|
||||
// Total size of the array
|
||||
int s = _primaryRenderInformation.count;
|
||||
|
||||
if (i + n <= s) {
|
||||
// The current index is small enough to just use one upload call
|
||||
upload(i, n);
|
||||
}
|
||||
else {
|
||||
// The current index is too close to the wrap around part, so we need
|
||||
// to split the upload into two parts:
|
||||
// 1. from the current index to the end of the array
|
||||
// 2. the rest starting from the beginning of the array
|
||||
int first = s - i;
|
||||
int second = n - first;
|
||||
upload(i, first); // 1
|
||||
upload(0, second); // 2
|
||||
}
|
||||
}
|
||||
else {
|
||||
// deltaT is negative, so the pointer is moving forwards
|
||||
|
||||
// The current index
|
||||
int i = _primaryRenderInformation.first;
|
||||
// Number of values
|
||||
int n = report.nUpdated + 1; // +1 for the floating position
|
||||
// Total size of the array
|
||||
int s = _primaryRenderInformation.count;
|
||||
|
||||
if (i + 1 >= n) {
|
||||
// The current index is big enough to fit everything into one call
|
||||
upload(i+1 - n, n);
|
||||
}
|
||||
else {
|
||||
// The current index is too close to the beginning of the array, so we
|
||||
// need to split the upload into two parts:
|
||||
// 1. from the beginning of the array to the current index
|
||||
// 2. filling the back of the array with the rest
|
||||
int b = n - (i + 1);
|
||||
upload(0, i + 1); // 1
|
||||
upload(s-b, b); // 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
RenderableTrailOrbit::UpdateReport RenderableTrailOrbit::updateTrails(
|
||||
const UpdateData& data)
|
||||
{
|
||||
// If we are doing a time jump, it is in general faster to recalculate everything
|
||||
// than to only update parts of the array
|
||||
if (data.isTimeJump) {
|
||||
_needsFullSweep = true;
|
||||
}
|
||||
if (_needsFullSweep) {
|
||||
fullSweep(data.time);
|
||||
return { true, UpdateReport::All } ;
|
||||
}
|
||||
|
||||
// When time stands still (at the iron hill), we don't need to perform any work
|
||||
if (data.delta == 0.0) {
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
double secondsPerPoint = _period / (_resolution - 1);
|
||||
// How much time has passed since the last permanent point
|
||||
double delta = data.time - _lastPointTime;
|
||||
|
||||
// We'd like to test for equality with 0 here, but due to rounding issues, we won't
|
||||
// get there. If this check is not here, we will trigger the positive or negative
|
||||
// branch below even though we don't have to
|
||||
//
|
||||
// This might become a bigger issue if we are starting to look at very short time
|
||||
// intervals
|
||||
const double Epsilon = 1e-7;
|
||||
if (abs(delta) < Epsilon) {
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
if (delta > 0.0) {
|
||||
// Check whether we need to drop a new permanent point. This is only the case if
|
||||
// enough (> secondsPerPoint) time has passed since the last permanent point
|
||||
if (abs(delta) < secondsPerPoint) {
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
// See how many points we need to drop
|
||||
int nNewPoints = floor(delta / secondsPerPoint);
|
||||
|
||||
// If we would need to generate more new points than there are total points in the
|
||||
// array, it is faster to regenerate the entire array
|
||||
if (nNewPoints >= _resolution) {
|
||||
fullSweep(data.time);
|
||||
return { true, UpdateReport::All };
|
||||
}
|
||||
|
||||
for (int i = 0; i < nNewPoints; ++i) {
|
||||
_lastPointTime += secondsPerPoint;
|
||||
|
||||
// Get the new permanent point and write it into the (previously) floating
|
||||
// location
|
||||
glm::vec3 p = _translation->position(_lastPointTime);
|
||||
_vertexArray[_primaryRenderInformation.first] = { p.x, p.y, p.z };
|
||||
|
||||
// Move the current pointer back one step to be used as the new floating
|
||||
// location
|
||||
--_primaryRenderInformation.first;
|
||||
// And loop around if necessary
|
||||
if (_primaryRenderInformation.first < 0) {
|
||||
_primaryRenderInformation.first += _primaryRenderInformation.count;
|
||||
}
|
||||
}
|
||||
|
||||
// The previously oldest permanent point has been moved nNewPoints steps into the
|
||||
// future
|
||||
_firstPointTime += nNewPoints * secondsPerPoint;
|
||||
|
||||
return { true, nNewPoints };
|
||||
}
|
||||
else {
|
||||
// See how many new points needs to be generated. Delta is negative, so we need
|
||||
// to invert the ratio
|
||||
int nNewPoints = -(floor(delta / secondsPerPoint));
|
||||
|
||||
// If we would need to generate more new points than there are total points in the
|
||||
// array, it is faster to regenerate the entire array
|
||||
if (nNewPoints >= _resolution) {
|
||||
fullSweep(data.time);
|
||||
return { true, UpdateReport::All };
|
||||
}
|
||||
|
||||
for (int i = 0; i < nNewPoints; ++i) {
|
||||
_firstPointTime -= secondsPerPoint;
|
||||
|
||||
// Get the new permanent point and write it into the (previously) floating
|
||||
// location
|
||||
glm::vec3 p = _translation->position(_firstPointTime);
|
||||
_vertexArray[_primaryRenderInformation.first] = { p.x, p.y, p.z };
|
||||
|
||||
// if we are on the upper bounds of the array, we start at 0
|
||||
if (_primaryRenderInformation.first == _primaryRenderInformation.count - 1) {
|
||||
// If it is at the beginning, set it to the end first
|
||||
_primaryRenderInformation.first = 0;
|
||||
}
|
||||
else {
|
||||
// Move the current pointer fowards one step to be used as the new floating
|
||||
++_primaryRenderInformation.first;
|
||||
}
|
||||
}
|
||||
|
||||
// The previously youngest point has become nNewPoints steps older
|
||||
_lastPointTime -= nNewPoints * secondsPerPoint;
|
||||
|
||||
return { true, -nNewPoints };
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableTrailOrbit::fullSweep(double time) {
|
||||
// Reserve the space for the vertices
|
||||
_vertexArray.clear();
|
||||
_vertexArray.resize(_resolution);
|
||||
|
||||
// The index buffer stays constant until we change the size of the array
|
||||
if (_indexBufferDirty) {
|
||||
// Create the index buffer and fill it with two ranges for [0, _resolution)
|
||||
_indexArray.clear();
|
||||
_indexArray.resize(_resolution * 2);
|
||||
std::iota(_indexArray.begin(), _indexArray.begin() + _resolution, 0);
|
||||
std::iota(_indexArray.begin() + _resolution, _indexArray.end(), 0);
|
||||
}
|
||||
|
||||
_lastPointTime = time;
|
||||
|
||||
double secondsPerPoint = _period / (_resolution - 1);
|
||||
// starting at 1 because the first position is a floating current one
|
||||
for (int i = 1; i < _resolution; ++i) {
|
||||
glm::vec3 p = _translation->position(time);
|
||||
_vertexArray[i] = { p.x, p.y, p.z };
|
||||
|
||||
time -= secondsPerPoint;
|
||||
}
|
||||
|
||||
_primaryRenderInformation.first = 0;
|
||||
_primaryRenderInformation.count = _resolution;
|
||||
|
||||
_firstPointTime = time + secondsPerPoint;
|
||||
_needsFullSweep = false;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
Reference in New Issue
Block a user