Skip to content

Commit

Permalink
Added LODGenerator class that uses MeshOptimizer
Browse files Browse the repository at this point in the history
to generate multiple levels of detail
for osg::Geometries and replaces them with a new
osg::LOD in the scene graph.
Added osgearth_lod application that uses
LODGenerator to generate lods and write them to
a new file.
  • Loading branch information
jasonbeverage committed May 13, 2024
1 parent cdaabe2 commit a9e75d3
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/applications/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ IF(NOT OSGEARTH_BUILD_PLATFORM_IPHONE)
if(OSGEARTH_BUILD_LEGACY_SPLAT_NODEKIT)
add_subdirectory(osgearth_exportgroundcover_splat)
endif()

if (OSGEARTH_HAVE_MESH_OPTIMIZER)
add_subdirectory(osgearth_lod)
endif()
ENDIF()

# Examples:
Expand Down
7 changes: 7 additions & 0 deletions src/applications/osgearth_lod/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGSIM_LIBRARY OSGSHADOW_LIBRARY OSGVIEWER_LIBRARY OSGGA_LIBRARY OSGTEXT_LIBRARY OSGMANIPULATOR_LIBRARY OPENTHREADS_LIBRARY)

SET(TARGET_SRC osgearth_lod.cpp )

#### end var setup ###
SETUP_APPLICATION(osgearth_lod)
60 changes: 60 additions & 0 deletions src/applications/osgearth_lod/osgearth_lod.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2020 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#define LC "[osgearth_lod] "

#include <osgEarth/Notify>
#include <osgEarth/LODGenerator>

#include <osg/ArgumentParser>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

using namespace osgEarth;

int
main(int argc, char** argv)
{
osg::ArgumentParser args(&argc, argv);

// Read the ouptut filename
std::string outFilename = "out.osgb";
args.read("--out", outFilename);

// Read the levels from the command line, formatted like --level threshold,range,aggressive
std::vector< LODGenerator::LODOptions> options;
float threshold = 0.0f;
float range = 0.0f;
bool aggressive = false;
while (args.read("--level", threshold, range, aggressive))
{
OE_NOTICE << "Adding level " << threshold << ", " << range << ", " << aggressive << std::endl;
options.push_back({ threshold, range, aggressive });
}

osg::ref_ptr<osg::Node> root = osgDB::readRefNodeFiles(args);

LODGenerator generator;
generator.generateLODs(root.get(), options);

osgDB::writeNodeFile(*root, outFilename);
return 0;
}
2 changes: 2 additions & 0 deletions src/osgEarth/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ SET(LIB_PUBLIC_HEADERS
LocalGeometryNode
LocalTangentPlane
Locators
LODGenerator
LogarithmicDepthBuffer
Map
MapboxGLGlyphManager
Expand Down Expand Up @@ -694,6 +695,7 @@ set(TARGET_SRC
LineSymbol.cpp
LocalGeometryNode.cpp
LocalTangentPlane.cpp
LODGenerator.cpp
LogarithmicDepthBuffer.cpp
Map.cpp
MapboxGLGlyphManager.cpp
Expand Down
33 changes: 33 additions & 0 deletions src/osgEarth/LODGenerator
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef OSGEARTH_LODGENERATOR_H
#define OSGEARTH_LODGENERATOR_H

#include <osgEarth/Common>
#include <osg/Node>

#ifdef OSGEARTH_HAVE_MESH_OPTIMIZER


namespace osgEarth
{
using namespace osgEarth;

class OSGEARTH_EXPORT LODGenerator
{
public:
struct LODOptions
{
float threshold;
float rangeFactor;
bool aggressive;
};

void generateLODs(osg::Node* node, const std::vector<LODOptions>& options);
};


}
#else
#pragma message("Warning: MeshOptimizer not available. LODGenerator will not be available.")
#endif

#endif
127 changes: 127 additions & 0 deletions src/osgEarth/LODGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include <osgEarth/LODGenerator>

#ifdef OSGEARTH_HAVE_MESH_OPTIMIZER

#include <osgEarth/NodeUtils>
#include <osgUtil/Optimizer>

#include <meshoptimizer.h>

using namespace osgEarth;

osg::LOD* createLODFromGeometry(osg::Geometry* originalGeometry, const std::vector<LODGenerator::LODOptions>& options)
{
double radius = originalGeometry->getBound().radius();

// Turn the geometry into an indexed mesh.
osgUtil::Optimizer o;
o.optimize(originalGeometry,
o.INDEX_MESH |
o.VERTEX_PRETRANSFORM |
o.VERTEX_POSTTRANSFORM);

osg::ref_ptr<osg::LOD> lodNode = new osg::LOD();

osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(originalGeometry->getVertexArray());
osg::DrawElements* drawElements = dynamic_cast<osg::DrawElements*>(originalGeometry->getPrimitiveSet(0));

if (!vertices || !drawElements) {
OE_WARN << "Invalid geometry data." << std::endl;
return nullptr;
}

size_t vertexCount = vertices->size();
size_t indexCount = drawElements->getNumIndices();
std::vector<unsigned int> indices(indexCount);
std::vector<float> vertexData(vertexCount * 3);

for (size_t i = 0; i < indexCount; ++i) {
indices[i] = drawElements->getElement(i);
}
for (size_t i = 0; i < vertexCount; ++i) {
vertexData[i * 3 + 0] = (*vertices)[i].x();
vertexData[i * 3 + 1] = (*vertices)[i].y();
vertexData[i * 3 + 2] = (*vertices)[i].z();
}


// Add the highest level of detail first
float minDistance = 0.0f;
float maxDistance = options[0].rangeFactor * radius;
lodNode->addChild(originalGeometry, minDistance, maxDistance);

float prevMaxDistance = maxDistance;

for (unsigned int level = 0; level < options.size(); ++level)
{
const LODGenerator::LODOptions& opt = options[level];

float target_error = 1e-2f;
float target_error_aggressive = 1e-1f;

size_t target_index_count = size_t(double(indexCount / 3) * opt.threshold) * 3;

unsigned int mesh_opt_flags = 0;

std::vector< unsigned int > newIndices(indices.size());
unsigned int newIndicesCount = meshopt_simplify(&newIndices[0], &indices[0], indexCount, &vertexData[0], vertexCount, sizeof(float) * 3, target_index_count, target_error, mesh_opt_flags);

if (opt.aggressive && newIndicesCount > target_index_count) {
newIndicesCount = meshopt_simplifySloppy(&newIndices[0], &indices[0], indexCount, &vertexData[0], vertexCount, sizeof(float) * 3, target_index_count, target_error_aggressive);
}

OE_INFO << "Simplified from " << indexCount / 3 << " triangles to target=" << target_index_count / 3 << " actual=" << newIndicesCount / 3 << " triangles" << std::endl;

newIndices.resize(newIndicesCount);

osg::ref_ptr<osg::Geometry> simplifiedGeometry = new osg::Geometry(*originalGeometry, osg::CopyOp::SHALLOW_COPY);
osg::ref_ptr<osg::DrawElementsUInt> newDrawElements = new osg::DrawElementsUInt(GL_TRIANGLES);


for (size_t i = 0; i < newIndices.size(); ++i) {
newDrawElements->push_back(newIndices[i]);
}

simplifiedGeometry->setVertexArray(vertices);
simplifiedGeometry->setPrimitiveSet(0, newDrawElements);

float minDistance = prevMaxDistance;
float maxDistance = level == options.size() - 1 ? FLT_MAX : options[level + 1].rangeFactor * radius;

lodNode->addChild(simplifiedGeometry, minDistance, maxDistance);
prevMaxDistance = maxDistance;
}

return lodNode.release();
}


void LODGenerator::generateLODs(osg::Node* node, const std::vector<LODOptions>& options)
{
FindNodesVisitor<osg::Geometry> nv;
node->accept(nv);

for (osg::Geometry* geom : nv._results)
{
osg::LOD* lod = createLODFromGeometry(geom, options);
if (lod)
{
osg::Group* parent = geom->getParent(0);
if (parent)
{
osg::Geode* geode = parent->asGeode();
if (geode)
{
lod->setStateSet(geode->getStateSet());
geode->getParent(0)->replaceChild(geode, lod);
}
else
{
parent->replaceChild(geom, lod);
}
}
}
}
}

#endif // OSGEARTH_HAVE_MESH_OPTIMIZER

0 comments on commit a9e75d3

Please sign in to comment.