Files
OpenSpace/modules/base/rendering/renderabletrailorbit.cpp
Alexander Bock acdfefe46d Merge branch 'master' into feature/loadingscreen-refactor
# Conflicts:
#	modules/base/rendering/renderabletrailorbit.h
#	modules/digitaluniverse/rendering/renderablebillboardscloud.cpp
#	modules/digitaluniverse/rendering/renderableplanescloud.cpp
#	modules/digitaluniverse/rendering/renderablepoints.cpp
#	modules/galaxy/rendering/renderablegalaxy.cpp
#	modules/galaxy/rendering/renderablegalaxy.h
#	modules/kameleonvolume/rendering/renderablekameleonvolume.cpp
#	modules/kameleonvolume/rendering/renderablekameleonvolume.h
#	modules/spacecraftinstruments/rendering/renderablefov.h
#	modules/spacecraftinstruments/rendering/renderableplaneprojection.h
#	modules/toyvolume/rendering/renderabletoyvolume.cpp
#	modules/toyvolume/rendering/renderabletoyvolume.h
#	modules/volume/rendering/renderabletimevaryingvolume.cpp
#	modules/volume/rendering/renderabletimevaryingvolume.h
2017-11-08 19:10:59 -06:00

459 lines
19 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* 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/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/scene/translation.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/opengl/programobject.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 {
static const openspace::properties::Property::PropertyInfo PeriodInfo = {
"Period",
"Period (in days)",
"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."
};
static const openspace::properties::Property::PropertyInfo ResolutionInfo = {
"Resolution",
"Number of samples along the orbit",
"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."
};
} // namespace
namespace openspace {
documentation::Documentation RenderableTrailOrbit::Documentation() {
using namespace documentation;
documentation::Documentation doc {
"RenderableTrailOrbit",
"base_renderable_renderabletrailorbit",
{
{
PeriodInfo.identifier,
new DoubleVerifier,
Optional::No,
PeriodInfo.description
},
{
ResolutionInfo.identifier,
new IntVerifier,
Optional::No,
ResolutionInfo.description
}
}
};
// Insert the parents documentation entries until we have a verifier that can deal
// with class hierarchy
documentation::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(PeriodInfo, 0.0, 0.0, 1e9)
, _resolution(ResolutionInfo, 10000, 1, 1000000)
, _needsFullSweep(true)
, _indexBufferDirty(true)
, _previousTime(0)
{
documentation::testSpecificationAndThrow(
Documentation(),
dictionary,
"RenderableTrailOrbit"
);
_translation->onParameterChange([this](){
_needsFullSweep = true;
});
// Period is in days
using namespace std::chrono;
const long long sph = duration_cast<seconds>(hours(24)).count();
_period = dictionary.value<double>(PeriodInfo.identifier) * sph;
_period.onChange([&] { _needsFullSweep = true; _indexBufferDirty = true; });
addProperty(_period);
_resolution = static_cast<int>(dictionary.value<double>(ResolutionInfo.identifier));
_resolution.onChange([&] { _needsFullSweep = true; _indexBufferDirty = true; });
addProperty(_resolution);
// We store the vertices with (excluding the wrapping) decending temporal order
_primaryRenderInformation.sorting = RenderInformation::VertexSorting::NewestFirst;
}
void RenderableTrailOrbit::initializeGL() {
RenderableTrail::initializeGL();
glGenVertexArrays(1, &_primaryRenderInformation._vaoID);
glGenBuffers(1, &_primaryRenderInformation._vBufferID);
glGenBuffers(1, &_primaryRenderInformation._iBufferID);
}
void RenderableTrailOrbit::deinitializeGL() {
glDeleteVertexArrays(1, &_primaryRenderInformation._vaoID);
glDeleteBuffers(1, &_primaryRenderInformation._vBufferID);
glDeleteBuffers(1, &_primaryRenderInformation._iBufferID);
RenderableTrail::deinitializeGL();
}
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
// 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.j2000Seconds());
_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 = std::abs(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, nullptr);
glBindVertexArray(0);
_previousTime = data.time.j2000Seconds();
}
RenderableTrailOrbit::UpdateReport RenderableTrailOrbit::updateTrails(
const UpdateData& data)
{
if (_needsFullSweep) {
fullSweep(data.time.j2000Seconds());
return { true, UpdateReport::All } ;
}
const double Epsilon = 1e-7;
// When time stands still (at the iron hill), we don't need to perform any work
if (std::abs(data.time.j2000Seconds() - _previousTime) < Epsilon) {
return { false, 0 };
}
double secondsPerPoint = _period / (_resolution - 1);
// How much time has passed since the last permanent point
double delta = data.time.j2000Seconds() - _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
if (std::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 (std::abs(delta) < secondsPerPoint) {
return { false, 0 };
}
// See how many points we need to drop
int nNewPoints = static_cast<int>(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.j2000Seconds());
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 = -(static_cast<int>(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.j2000Seconds());
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