Verified Commit 0a0771a4 authored by Patrick Avery's avatar Patrick Avery
Browse files

Add poly LOD writing to vtkJSONSceneExporter



This adds options to write polyLODs in a series of decreasing
resolution directories. The directories do not get zipped here, but
are intended to be zipped in subclasses, including the vtkPVWebExporter
in ParaView. The zipped files are intended to be uploaded to the web,
and with some new additions to VTK.js, they will be downloaded one at
a time, which has the effect of increasing the resolution of the polydata
in VTK.js over time.

For the quadric clustering, the initial guess for the number of
divisions may be improved upon, as well as some of the math involved.
The goal is for each directory to be roughly 1/4 the size of the
previous one, but it is not always straightforward to do when using the
vtkQuadricClustering filter.

Signed-off-by: Patrick Avery's avatarPatrick Avery <patrick.avery@kitware.com>
parent 23a15753
......@@ -31,11 +31,11 @@
#include "vtkProp.h"
#include "vtkPropCollection.h"
#include "vtkProperty.h"
#include "vtkQuadricClustering.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkRendererCollection.h"
#include "vtkScalarsToColors.h"
#include "vtkSmartPointer.h"
#include "vtkTexture.h"
#include "vtksys/FStream.hxx"
#include "vtksys/SystemTools.hxx"
......@@ -55,6 +55,9 @@ vtkJSONSceneExporter::vtkJSONSceneExporter()
this->WriteTextureLODs = false;
this->TextureLODsBaseSize = 100000;
this->TextureLODsBaseUrl = nullptr;
this->WritePolyLODs = false;
this->PolyLODsBaseSize = 100000;
this->PolyLODsBaseUrl = nullptr;
}
// ----------------------------------------------------------------------------
......@@ -194,6 +197,16 @@ std::string vtkJSONSceneExporter::WriteDataSet(vtkDataSet* dataset, const char*
std::string dsPath = this->CurrentDataSetPath();
++this->DatasetCount;
auto* polyData = vtkPolyData::SafeDownCast(dataset);
vtkSmartPointer<vtkPolyData> newDataset;
std::string polyLODsConfig;
if (this->WritePolyLODs && polyData)
{
newDataset = this->WritePolyLODSeries(polyData, polyLODsConfig);
// Write the smallest poly LOD to the vtkjs file
dataset = newDataset.Get();
}
vtkNew<vtkJSONDataSetWriter> dsWriter;
dsWriter->SetInputData(dataset);
dsWriter->SetFileName(dsPath.c_str());
......@@ -225,6 +238,7 @@ std::string vtkJSONSceneExporter::WriteDataSet(vtkDataSet* dataset, const char*
meta << addOnMeta;
}
meta << polyLODsConfig;
meta << INDENT << "}";
return meta.str();
......@@ -287,6 +301,7 @@ void vtkJSONSceneExporter::WriteData()
this->DatasetCount = 0;
this->TextureStrings.clear();
this->TextureLODStrings.clear();
this->FilesToZip.clear();
// make sure the user specified a FileName or FilePointer
if (this->FileName == nullptr)
......@@ -527,6 +542,183 @@ std::string vtkJSONSceneExporter::WriteTextureLODSeries(vtkTexture* texture)
// ----------------------------------------------------------------------------
vtkSmartPointer<vtkPolyData> vtkJSONSceneExporter::WritePolyLODSeries(
vtkPolyData* dataset, std::string& polyLODsConfig)
{
vtkSmartPointer<vtkPolyData> polyData = dataset;
std::vector<std::string> files;
// Write these into the parent directory of our file.
// This next line also converts the path to unix slashes.
vtkNew<vtkJSONDataSetWriter> dsWriter;
std::string path = vtksys::SystemTools::GetParentDirectory(this->FileName) + "/";
path = vtksys::SystemTools::ConvertToOutputPath(path);
// If the new size is not at least 5% different from the old size,
// stop writing out the LODs, because the difference is too small.
size_t previousDataSize = 0;
double minDiffFraction = 0.05;
const size_t& baseSize = this->PolyLODsBaseSize;
int count = 0;
while (true)
{
// Squeeze the data, or we won't get an accurate memory size
polyData->Squeeze();
auto dataSize = polyData->GetActualMemorySize();
bool tooSimilar = false;
if (previousDataSize != 0)
{
double fraction = (static_cast<double>(previousDataSize) - dataSize) / previousDataSize;
if (fabs(fraction) < minDiffFraction)
{
tooSimilar = true;
}
}
previousDataSize = dataSize;
if (static_cast<size_t>(dataSize) * 1000 <= baseSize || tooSimilar)
{
// It is either now below the base size, or the size isn't
// changing much anymore.
// The latest "polyData" will be written into the .vtkjs directory
break;
}
// Write out the source LOD
// They are not zipped yet, but they should be zipped by subclasses
std::string name =
"sourceLOD_" + std::to_string(this->DatasetCount) + "_" + std::to_string(++count) + ".zip";
std::string full_path = path + name;
dsWriter->SetInputData(polyData);
dsWriter->SetFileName(full_path.c_str());
dsWriter->Write();
files.push_back(name);
this->FilesToZip.push_back(full_path);
// Now reduce the size of the data
double bounds[6];
polyData->GetBounds(bounds);
double length = polyData->GetLength();
double factors[3] = { (bounds[1] - bounds[0]) / length + 0.01,
(bounds[3] - bounds[2]) / length + 0.01, (bounds[5] - bounds[4]) / length + 0.01 };
double factorsCube = factors[0] * factors[1] * factors[2];
// Try to make a good first guess for B
// TODO: this first guess can probably be improved a lot.
double B = pow(100 * dataSize / factorsCube, 0.3333);
vtkNew<vtkQuadricClustering> cc;
cc->UseInputPointsOn();
cc->CopyCellDataOn();
cc->SetInputDataObject(polyData);
cc->SetAutoAdjustNumberOfDivisions(false);
// We will try to get the next size to be between 1/3 and 1/5
// of the original. The goal is to be approximately 1/4.
auto targetSize = dataSize / 4;
auto targetMin = dataSize / 5;
auto targetMax = dataSize / 3;
int maxAttempts = 100;
size_t previousSize = 0;
// If we fail to get to ~1/4 the size for some reason, just use
// the default divisions. Sometimes, a failure is caused by
// one of the factors being too big.
bool useDefaultDivisions = false;
for (int numAttempts = 0; numAttempts < maxAttempts; ++numAttempts)
{
cc->SetNumberOfXDivisions(B * factors[0] + 1);
cc->SetNumberOfYDivisions(B * factors[1] + 1);
cc->SetNumberOfZDivisions(B * factors[2] + 1);
try
{
cc->Update();
}
catch (const std::bad_alloc&)
{
// Too many divisions, probably. Just use the defaults.
useDefaultDivisions = true;
break;
}
// Squeeze the data, or we won't get an accurate memory size
cc->GetOutput()->Squeeze();
auto newSize = cc->GetOutput()->GetActualMemorySize();
if (newSize == previousSize)
{
// This is not changing. Just use the default divisions.
useDefaultDivisions = true;
break;
}
previousSize = newSize;
if (newSize >= targetMin && newSize <= targetMax)
{
// We are within the tolerance!
break;
}
else
{
// Figure out the fraction that we are off, and change B
// accordingly
double fraction = newSize / static_cast<double>(targetSize);
B /= pow(fraction, 0.333);
}
}
if (useDefaultDivisions)
{
vtkNew<vtkQuadricClustering> defaultCC;
defaultCC->UseInputPointsOn();
defaultCC->CopyCellDataOn();
defaultCC->SetInputDataObject(polyData);
defaultCC->Update();
polyData = defaultCC->GetOutput();
}
else
{
polyData = cc->GetOutput();
}
}
// Write out the config
const char* url = this->PolyLODsBaseUrl;
std::string baseUrl = url ? url : "";
const char* INDENT = " ";
std::stringstream config;
config << ",\n"
<< INDENT << "\"sourceLODs\": {\n"
<< INDENT << " \"baseUrl\": \"" << baseUrl << "\",\n"
<< INDENT << " \"files\": [\n";
// Reverse the order of the files so the smallest comes first
std::reverse(files.begin(), files.end());
for (size_t i = 0; i < files.size(); ++i)
{
config << INDENT << " \"" << files[i] << "\"";
if (i != files.size() - 1)
{
config << ",\n";
}
else
{
config << "\n";
}
}
config << INDENT << " ]\n" << INDENT << "}";
polyLODsConfig = config.str();
return polyData;
}
// ----------------------------------------------------------------------------
void vtkJSONSceneExporter::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
......
......@@ -27,13 +27,16 @@
#include "vtkExporter.h"
#include "vtkIOExportModule.h" // For export macro
#include "vtkSmartPointer.h"
#include <map> // For string parameter
#include <string> // For string parameter
#include <vector>
class vtkActor;
class vtkDataObject;
class vtkDataSet;
class vtkPolyData;
class vtkScalarsToColors;
class vtkTexture;
......@@ -96,6 +99,46 @@ public:
vtkGetStringMacro(TextureLODsBaseUrl);
//@}
//@{
/**
* Whether or not to write poly LODs.
* This will write out the poly LOD sources in a series of decreasing
* resolution data sets, which are intended to be uploaded to the
* web. vtkQuadricCluster is used to decrease the resolution of the
* poly data. Each will be approximately 1/4 the size of the previous
* one (unless certain errors occur, and then the defaults for quadric
* clustering will be used, which will produce an unknown size). The
* files will stop being written out when one is smaller than the
* PolyLODsBaseSize, or if the difference in the sizes of the two
* most recent LODs is less than 5%. The smallest LOD will be written
* into the vtkjs file, rather than with the rest of the LODs.
* Default is false.
*/
vtkSetMacro(WritePolyLODs, bool);
vtkGetMacro(WritePolyLODs, bool);
//@}
//@{
/**
* The base size to be used for poly LODs. The poly LODs will stop
* being written out when one is smaller than this size, or if the
* difference in the sizes of the two most recent LODs is less
* than 5%.
* Default is 100 KB. Units are in bytes.
*/
vtkSetMacro(PolyLODsBaseSize, size_t);
vtkGetMacro(PolyLODsBaseSize, size_t);
//@}
//@{
/**
* The base URL to be used for poly LODs.
* Default is nullptr.
*/
vtkSetStringMacro(PolyLODsBaseUrl);
vtkGetStringMacro(PolyLODsBaseUrl);
//@}
protected:
vtkJSONSceneExporter();
~vtkJSONSceneExporter() override;
......@@ -112,16 +155,26 @@ protected:
std::string WriteTexture(vtkTexture* texture);
std::string WriteTextureLODSeries(vtkTexture* texture);
// The returned pointer is the smallest poly LOD, intended to be
// written out in the vtkjs file.
vtkSmartPointer<vtkPolyData> WritePolyLODSeries(vtkPolyData* polys, std::string& config);
char* FileName;
bool WriteTextures;
bool WriteTextureLODs;
size_t TextureLODsBaseSize;
char* TextureLODsBaseUrl;
bool WritePolyLODs;
size_t PolyLODsBaseSize;
char* PolyLODsBaseUrl;
int DatasetCount;
std::map<std::string, std::string> LookupTables;
std::map<vtkTexture*, std::string> TextureStrings;
std::map<vtkTexture*, std::string> TextureLODStrings;
// Files that subclasses should zip
std::vector<std::string> FilesToZip;
private:
vtkJSONSceneExporter(const vtkJSONSceneExporter&) = delete;
void operator=(const vtkJSONSceneExporter&) = delete;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment