mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-03-03 10:58:34 -06:00
Distant dependent LOD geometry. Demo purpose only.
This commit is contained in:
@@ -22,22 +22,71 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <ghoul/misc/assert.h>
|
||||
|
||||
#include <modules/globebrowsing/datastructures/chunknode.h>
|
||||
#include <modules/globebrowsing/rendering/chunklodglobe.h>
|
||||
#include <modules/globebrowsing/util/converter.h>
|
||||
|
||||
namespace {
|
||||
const std::string _loggerCat = "ChunkNode";
|
||||
}
|
||||
|
||||
namespace openspace {
|
||||
|
||||
BoundingRect::BoundingRect(Scalar cx, Scalar cy, Scalar hsx, Scalar hsy)
|
||||
: center(Vec2(cx, cy)), halfSize(Vec2(hsx, hsy)) { }
|
||||
: center(Vec2(cx, cy))
|
||||
, halfSize(Vec2(hsx, hsy))
|
||||
, _hasCachedCartesianCenter(false)
|
||||
, _hasCachedArea(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BoundingRect::BoundingRect(const Vec2& center, const Vec2& halfSize)
|
||||
:center(center), halfSize(halfSize) { }
|
||||
:center(center)
|
||||
, halfSize(halfSize)
|
||||
, _hasCachedCartesianCenter(false)
|
||||
, _hasCachedArea(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
Vec3 BoundingRect::centerAsCartesian(Scalar radius) {
|
||||
if (!_hasCachedCartesianCenter) {
|
||||
_cartesianCenter = converter::latLonToCartesian(center.x, center.y, 1);
|
||||
_hasCachedCartesianCenter = true;
|
||||
}
|
||||
return radius * _cartesianCenter;
|
||||
}
|
||||
|
||||
|
||||
Scalar BoundingRect::area(Scalar radius) {
|
||||
if (!_hasCachedArea) {
|
||||
Scalar deltaTheta = 2 * halfSize.y; // longitude range
|
||||
Scalar phiMin = center.x - halfSize.x;
|
||||
Scalar phiMax = center.x + halfSize.x;
|
||||
_area = deltaTheta * (sin(phiMax) - sin(phiMin));
|
||||
_hasCachedArea = true;
|
||||
}
|
||||
return radius*radius*_area;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int ChunkNode::instanceCount = 0;
|
||||
|
||||
ChunkNode::ChunkNode(ChunkLodGlobe& owner, const BoundingRect& bounds, ChunkNode* parent)
|
||||
: _owner(owner)
|
||||
, bounds(bounds)
|
||||
@@ -47,12 +96,15 @@ ChunkNode::ChunkNode(ChunkLodGlobe& owner, const BoundingRect& bounds, ChunkNode
|
||||
_children[1] = nullptr;
|
||||
_children[2] = nullptr;
|
||||
_children[3] = nullptr;
|
||||
instanceCount++;
|
||||
}
|
||||
|
||||
ChunkNode::~ChunkNode() {
|
||||
|
||||
instanceCount--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool ChunkNode::isRoot() const {
|
||||
return _parent == nullptr;
|
||||
}
|
||||
@@ -88,9 +140,45 @@ bool ChunkNode::isReady() const{
|
||||
}
|
||||
|
||||
void ChunkNode::render(const RenderData& data) {
|
||||
ghoul_assert(isRoot(), "this method should only be invoked on root");
|
||||
//LDEBUG("-------------");
|
||||
internalUpdateChunkTree(data, 0);
|
||||
internalRender(data, 0);
|
||||
}
|
||||
|
||||
|
||||
// Returns true or false wether this node can be merge or not
|
||||
bool ChunkNode::internalUpdateChunkTree(const RenderData& data, int depth) {
|
||||
if (isLeaf()) {
|
||||
int desiredDepth = desiredSplitDepth(data);
|
||||
if (desiredDepth > depth) {
|
||||
split();
|
||||
}
|
||||
else if(desiredDepth < depth){
|
||||
return true; // request a merge from parent
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
int requestedMergeMask = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (_children[i]->internalUpdateChunkTree(data, depth + 1)) {
|
||||
requestedMergeMask |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
// check if all children requested merge
|
||||
if (requestedMergeMask == 0xf) {
|
||||
merge();
|
||||
|
||||
// re-run this method on this, now that this is a leaf node
|
||||
return internalUpdateChunkTree(data, depth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ChunkNode::internalRender(const RenderData& data, int currLevel) {
|
||||
if (isLeaf()) {
|
||||
LatLonPatch& templatePatch = _owner.getTemplatePatch();
|
||||
@@ -105,8 +193,39 @@ void ChunkNode::internalRender(const RenderData& data, int currLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
int ChunkNode::desiredSplitDepth(const RenderData& data) {
|
||||
Vec3 normal = bounds.centerAsCartesian(1.0);
|
||||
Vec3 pos = data.position.dvec3() + _owner.globeRadius * normal;
|
||||
|
||||
// Temporay ugly fix for Camera::position() is broken.
|
||||
Vec3 buggedCameraPos = data.camera.position().dvec3();
|
||||
Vec3 cameraDirection = Vec3(data.camera.viewDirection());
|
||||
Vec3 cameraPos = buggedCameraPos - _owner.globeRadius * cameraDirection;
|
||||
Vec3 cameraToChunk = pos - cameraPos;
|
||||
|
||||
// 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(normal, cameraDirection);
|
||||
if (cosNormalCameraDirection > 0.3) {
|
||||
return _owner.minSplitDepth;
|
||||
}
|
||||
|
||||
|
||||
Scalar distance = glm::length(cameraToChunk) + _owner.globeRadius;
|
||||
_owner.minDistToCamera = fmin(_owner.minDistToCamera, distance);
|
||||
|
||||
int depthRange = _owner.maxSplitDepth - _owner.minSplitDepth;
|
||||
|
||||
Scalar scaleFactor = depthRange * _owner.globeRadius * 25*bounds.area(1);
|
||||
|
||||
int desiredDepth = _owner.minSplitDepth + floor(scaleFactor / distance);
|
||||
return glm::clamp(desiredDepth, _owner.minSplitDepth, _owner.maxSplitDepth);
|
||||
}
|
||||
|
||||
|
||||
void ChunkNode::update(const UpdateData& data) {
|
||||
internalUpdate(data, 0);
|
||||
ghoul_assert(isRoot(), "this method should only be invoked on root");
|
||||
//internalUpdate(data, 0);
|
||||
}
|
||||
|
||||
void ChunkNode::internalUpdate(const UpdateData& data, int currLevel) {
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace openspace {
|
||||
// Using double precision
|
||||
typedef double Scalar;
|
||||
typedef glm::dvec2 Vec2;
|
||||
typedef glm::dvec3 Vec3;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
@@ -58,8 +59,20 @@ enum Quad {
|
||||
struct BoundingRect {
|
||||
BoundingRect(Scalar, Scalar, Scalar, Scalar);
|
||||
BoundingRect(const Vec2& center, const Vec2& halfSize);
|
||||
|
||||
Vec3 centerAsCartesian(Scalar radius);
|
||||
Scalar area(Scalar radius);
|
||||
|
||||
Vec2 center;
|
||||
Vec2 halfSize;
|
||||
|
||||
|
||||
private:
|
||||
bool _hasCachedCartesianCenter;
|
||||
Vec3 _cartesianCenter;
|
||||
|
||||
bool _hasCachedArea;
|
||||
Scalar _area;
|
||||
};
|
||||
|
||||
|
||||
@@ -79,8 +92,6 @@ public:
|
||||
bool isLeaf() const;
|
||||
|
||||
const ChunkNode& getChild(Quad quad) const;
|
||||
const BoundingRect bounds;
|
||||
|
||||
|
||||
|
||||
bool initialize() override;
|
||||
@@ -90,10 +101,16 @@ public:
|
||||
void render(const RenderData& data) override;
|
||||
void update(const UpdateData& data) override;
|
||||
|
||||
void internalRender(const RenderData& data, int currLevel);
|
||||
void internalUpdate(const UpdateData& data, int currLevel);
|
||||
BoundingRect bounds;
|
||||
|
||||
static int instanceCount;
|
||||
|
||||
private:
|
||||
|
||||
void internalRender(const RenderData& data, int currLevel);
|
||||
void internalUpdate(const UpdateData& data, int currLevel);
|
||||
bool internalUpdateChunkTree(const RenderData& data, int currLevel);
|
||||
int desiredSplitDepth(const RenderData& data);
|
||||
|
||||
|
||||
ChunkNode* _parent;
|
||||
@@ -101,6 +118,8 @@ private:
|
||||
|
||||
ChunkLodGlobe& _owner;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -58,12 +58,14 @@ namespace openspace {
|
||||
: _leftRoot(new ChunkNode(*this, LEFT_HEMISPHERE))
|
||||
, _rightRoot(new ChunkNode(*this, RIGHT_HEMISPHERE))
|
||||
, _templatePatch(10,10, 0, 0, 0, 0)
|
||||
, globeRadius(6.3e6)
|
||||
, minSplitDepth(1)
|
||||
, maxSplitDepth(7)
|
||||
, _rotation("rotation", "Rotation", 0, 0, 360)
|
||||
{
|
||||
std::string name;
|
||||
bool success = dictionary.getValue(SceneGraphNode::KeyName, name);
|
||||
ghoul_assert(success,
|
||||
"ChunkLodGlobe need the '" << SceneGraphNode::KeyName << "' be specified");
|
||||
//ghoul_assert(success, "ChunkLodGlobe need the '" << SceneGraphNode::KeyName << "' be specified");
|
||||
setName(name);
|
||||
|
||||
dictionary.getValue(keyFrame, _frame);
|
||||
@@ -74,10 +76,8 @@ namespace openspace {
|
||||
// Mainly for debugging purposes @AA
|
||||
addProperty(_rotation);
|
||||
|
||||
|
||||
|
||||
// ----- specific for this class only ------ //
|
||||
_leftRoot->split(5);
|
||||
_rightRoot->split(5);
|
||||
}
|
||||
|
||||
ChunkLodGlobe::~ChunkLodGlobe() {
|
||||
@@ -110,13 +110,17 @@ namespace openspace {
|
||||
return ready;
|
||||
}
|
||||
|
||||
void ChunkLodGlobe::render(const RenderData& data)
|
||||
{
|
||||
// Set patch to follow camera
|
||||
//_patch.setPositionLatLon(converter::cartesianToLatLon(data.camera.position().dvec3()));
|
||||
// render
|
||||
void ChunkLodGlobe::render(const RenderData& data){
|
||||
minDistToCamera = INFINITY;
|
||||
_leftRoot->render(data);
|
||||
_rightRoot->render(data);
|
||||
|
||||
//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("ChunkNode count: " << ChunkNode::instanceCount);
|
||||
}
|
||||
|
||||
void ChunkLodGlobe::update(const UpdateData& data) {
|
||||
|
||||
@@ -27,14 +27,16 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
|
||||
|
||||
// open space includes
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
|
||||
#include <modules/globebrowsing/datastructures/chunknode.h>
|
||||
|
||||
#include <modules/globebrowsing/rendering/geometry.h>
|
||||
#include <modules/globebrowsing/rendering/gridgeometry.h>
|
||||
#include <modules/globebrowsing/rendering/distanceswitch.h>
|
||||
@@ -63,6 +65,12 @@ namespace openspace {
|
||||
void render(const RenderData& data) override;
|
||||
void update(const UpdateData& data) override;
|
||||
|
||||
double minDistToCamera;
|
||||
|
||||
const Scalar globeRadius;
|
||||
|
||||
const int minSplitDepth;
|
||||
const int maxSplitDepth;
|
||||
|
||||
|
||||
private:
|
||||
@@ -75,6 +83,7 @@ namespace openspace {
|
||||
|
||||
// the patch used for actual rendering
|
||||
LatLonPatch _templatePatch;
|
||||
|
||||
|
||||
static const BoundingRect LEFT_HEMISPHERE;
|
||||
static const BoundingRect RIGHT_HEMISPHERE;
|
||||
|
||||
@@ -104,6 +104,7 @@ namespace openspace {
|
||||
}
|
||||
|
||||
void LatLonPatch::render(const RenderData& data) {
|
||||
|
||||
// activate shader
|
||||
_programObject->activate();
|
||||
|
||||
@@ -116,22 +117,22 @@ namespace openspace {
|
||||
glm::dvec3 p00, p01, p10, p11;
|
||||
|
||||
// Calculate global positions of control points
|
||||
p00 = glm::dvec3(converter::latLonToCartesian(
|
||||
p00 = converter::latLonToCartesian(
|
||||
_posLatLon.x - _sizeLatLon.x,
|
||||
_posLatLon.y - _sizeLatLon.y,
|
||||
r));
|
||||
p10 = glm::dvec3(converter::latLonToCartesian(
|
||||
r);
|
||||
p10 = converter::latLonToCartesian(
|
||||
_posLatLon.x + _sizeLatLon.x,
|
||||
_posLatLon.y - _sizeLatLon.y,
|
||||
r));
|
||||
p01 = glm::dvec3(converter::latLonToCartesian(
|
||||
r);
|
||||
p01 = converter::latLonToCartesian(
|
||||
_posLatLon.x - _sizeLatLon.x,
|
||||
_posLatLon.y + _sizeLatLon.y,
|
||||
r));
|
||||
p11 = glm::dvec3(converter::latLonToCartesian(
|
||||
r);
|
||||
p11 = converter::latLonToCartesian(
|
||||
_posLatLon.x + _sizeLatLon.x,
|
||||
_posLatLon.y + _sizeLatLon.y,
|
||||
r));
|
||||
r);
|
||||
|
||||
// TODO : Transformation to world space from model space should also consider
|
||||
// rotations. Now it only uses translatation for simplicity. Should be done
|
||||
@@ -172,9 +173,12 @@ namespace openspace {
|
||||
_programObject->setUniform("Projection", data.camera.projectionMatrix());
|
||||
|
||||
// Render triangles (use texture coordinates to interpolate to new positions)
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
|
||||
|
||||
|
||||
// render
|
||||
_grid.drawUsingActiveProgram();
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace openspace {
|
||||
|
||||
namespace converter {
|
||||
|
||||
|
||||
glm::dvec3 latLonToCartesian(double latitude, double longitude, double radius) {
|
||||
return radius * glm::dvec3(
|
||||
glm::cos(latitude) * glm::cos(longitude),
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
#include <openspace/../modules/globebrowsing/datastructures/chunknode.h>
|
||||
#include <modules/globebrowsing/rendering/chunklodglobe.h>
|
||||
#include <modules/globebrowsing/datastructures/chunknode.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <glm/glm.hpp>
|
||||
@@ -34,26 +35,39 @@ using namespace openspace;
|
||||
|
||||
class ChunkNodeTest : public testing::Test {};
|
||||
|
||||
/*
|
||||
TEST_F(ChunkNodeTest, Split) {
|
||||
|
||||
ghoul::Dictionary dict;
|
||||
ChunkLodGlobe chunkLodNode(dict);
|
||||
chunkLodNode.initialize();
|
||||
|
||||
BoundingRect bounds(Vec2(2, 2), Vec2(2, 2));
|
||||
auto cn = std::shared_ptr<ChunkNode>(new ChunkNode(bounds));
|
||||
ASSERT_TRUE(cn->isRoot()) << "Chunk node is root";
|
||||
ASSERT_TRUE(cn->isLeaf()) << "Chunk node is leaf";
|
||||
ChunkNode cn(chunkLodNode,bounds);
|
||||
ASSERT_TRUE(cn.isRoot()) << "Chunk node is root";
|
||||
ASSERT_TRUE(cn.isLeaf()) << "Chunk node is leaf";
|
||||
|
||||
cn->split();
|
||||
ASSERT_TRUE(cn->isRoot()) << "Chunk node is root";
|
||||
ASSERT_FALSE(cn->isLeaf()) << "Chunk node is not leaf";
|
||||
cn.split();
|
||||
ASSERT_TRUE(cn.isRoot()) << "Chunk node is root";
|
||||
ASSERT_FALSE(cn.isLeaf()) << "Chunk node is not leaf";
|
||||
|
||||
ASSERT_EQ(cn->bounds.center.x, cn->getChild(Quad::NORTH_WEST).bounds.center.x * 2);
|
||||
ASSERT_EQ(cn->bounds.center.x, cn->getChild(Quad::NORTH_EAST).bounds.center.x * 2/3);
|
||||
ASSERT_EQ(cn.bounds.center.x, cn.getChild(Quad::NORTH_WEST).bounds.center.x * 2);
|
||||
ASSERT_EQ(cn.bounds.center.x, cn.getChild(Quad::NORTH_EAST).bounds.center.x * 2/3);
|
||||
|
||||
ASSERT_EQ(cn->bounds.halfSize.x, cn->getChild(Quad::NORTH_WEST).bounds.halfSize.x * 2);
|
||||
ASSERT_EQ(cn->bounds.halfSize.y, cn->getChild(Quad::NORTH_WEST).bounds.halfSize.y * 2);
|
||||
ASSERT_EQ(cn.bounds.halfSize.x, cn.getChild(Quad::NORTH_WEST).bounds.halfSize.x * 2);
|
||||
ASSERT_EQ(cn.bounds.halfSize.y, cn.getChild(Quad::NORTH_WEST).bounds.halfSize.y * 2);
|
||||
|
||||
chunkLodNode.deinitialize();
|
||||
}
|
||||
|
||||
TEST_F(ChunkNodeTest, Merge) {
|
||||
ghoul::Dictionary dict;
|
||||
ChunkLodGlobe chunkLodNode(dict);
|
||||
chunkLodNode.initialize();
|
||||
|
||||
|
||||
BoundingRect bounds(Vec2(2, 2), Vec2(2, 2));
|
||||
ChunkNode cn(bounds);
|
||||
ChunkNode cn(chunkLodNode,bounds);
|
||||
ASSERT_TRUE(cn.isRoot()) << "Chunk node is root";
|
||||
ASSERT_TRUE(cn.isLeaf()) << "Chunk node is leaf";
|
||||
|
||||
@@ -65,4 +79,7 @@ TEST_F(ChunkNodeTest, Merge) {
|
||||
ASSERT_TRUE(cn.isRoot()) << "Chunk node is root";
|
||||
ASSERT_TRUE(cn.isLeaf()) << "Chunk node is leaf";
|
||||
|
||||
}
|
||||
chunkLodNode.deinitialize();
|
||||
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user