Commit e27ccdab authored by Ken Martin's avatar Ken Martin
Browse files

add simple gltf exporter to VTK

and a simple test for it
parent adba7c8c
set(Module_SRCS
vtkExporter.cxx
vtkGL2PSExporter.cxx
vtkGLTFExporter.cxx
vtkIVExporter.cxx
vtkOBJExporter.cxx
vtkOOGLExporter.cxx
......
......@@ -34,6 +34,7 @@ endif()
vtk_add_test_cxx(vtkIOExportCxxTests tests
X3DTest.cxx,NO_DATA,NO_VALID
TestOBJExporter.cxx,NO_DATA,NO_VALID
TestGLTFExporter.cxx,NO_DATA,NO_VALID
TestSingleVTPExporter.cxx,NO_DATA,NO_VALID
${GL2PSTests} ${GL2PSTestsPDF} ${SVGTests}
TestRIBExporter.cxx,NO_VALID
......
/*=========================================================================
Program: Visualization Toolkit
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkActor.h"
#include "vtkElevationFilter.h"
#include "vtkGLTFExporter.h"
#include "vtkNew.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkSphereSource.h"
#include "vtkTestUtilities.h"
#include <cstdlib>
namespace {
size_t fileSize(const std::string & filename)
{
size_t size = 0;
FILE* f = fopen(filename.c_str(), "r");
if (f)
{
fseek(f, 0, SEEK_END);
size = ftell(f);
fclose(f);
}
else
{
std::cerr << "Error: cannot open file " << filename << std::endl;
}
return size;
}
}
int TestGLTFExporter(int argc, char *argv[])
{
char *tempDir = vtkTestUtilities::GetArgOrEnvOrDefault(
"-T", argc, argv, "VTK_TEMP_DIR", "Testing/Temporary");
if (!tempDir)
{
std::cout << "Could not determine temporary directory.\n";
return EXIT_FAILURE;
}
std::string testDirectory = tempDir;
delete[] tempDir;
std::string filename = testDirectory
+ std::string("/") + std::string("Export");
vtkNew<vtkSphereSource> sphere;
vtkNew<vtkElevationFilter> elev;
elev->SetInputConnection(sphere->GetOutputPort());
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(elev->GetOutputPort());
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
vtkNew<vtkRenderer> renderer;
renderer->AddActor(actor);
vtkNew<vtkRenderWindow> window;
window->AddRenderer(renderer);
window->Render();
filename += ".gltf";
vtkNew<vtkGLTFExporter> exporter;
exporter->SetRenderWindow(window);
exporter->SetFileName(filename.c_str());
exporter->Write();
size_t correctSize = fileSize(filename);
if (correctSize == 0)
{
return EXIT_FAILURE;
}
actor->VisibilityOff();
exporter->Write();
size_t noDataSize = fileSize(filename);
if (noDataSize == 0)
{
return EXIT_FAILURE;
}
if (noDataSize >= correctSize)
{
std::cerr << "Error: file should contain data for a visible actor"
"and not for a hidden one." << std::endl;
return EXIT_FAILURE;
}
actor->VisibilityOn();
actor->SetMapper(nullptr);
exporter->Write();
size_t size = fileSize(filename);
if (size == 0)
{
return EXIT_FAILURE;
}
if (size > noDataSize)
{
std::cerr << "Error: file should not contain geometry"
" (actor has no mapper)" << std::endl;
return EXIT_FAILURE;
}
actor->SetMapper(mapper);
mapper->RemoveAllInputConnections(0);
exporter->Write();
size = fileSize(filename);
if (size == 0)
{
return EXIT_FAILURE;
}
if (size > noDataSize)
{
std::cerr << "Error: file should not contain geometry"
" (mapper has no input)" << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
......@@ -38,4 +38,5 @@ vtk_module(vtkIOExport
vtkIOImage
vtkImagingCore
vtksys
vtkjsoncpp
)
/*=========================================================================
Program: Visualization Toolkit
Module: vtkGLTFExporter.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkGLTFExporter.h"
#include "vtk_jsoncpp.h"
#include "vtkAssemblyPath.h"
#include "vtkCamera.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkMapper.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkRange.h"
#include "vtkRendererCollection.h"
#include "vtkRenderWindow.h"
#include "vtkTriangleFilter.h"
#include "vtksys/SystemTools.hxx"
vtkStandardNewMacro(vtkGLTFExporter);
vtkGLTFExporter::vtkGLTFExporter()
{
this->FileName = nullptr;
}
vtkGLTFExporter::~vtkGLTFExporter()
{
delete [] this->FileName;
}
namespace {
vtkPolyData *findPolyData(vtkDataObject* input)
{
// do we have polydata?
vtkPolyData *pd = vtkPolyData::SafeDownCast(input);
if (pd)
{
return pd;
}
vtkCompositeDataSet *cd = vtkCompositeDataSet::SafeDownCast(input);
if (cd)
{
vtkSmartPointer<vtkCompositeDataIterator> iter;
iter.TakeReference(cd->NewIterator());
for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
{
pd = vtkPolyData::SafeDownCast(iter->GetCurrentDataObject());
if (pd)
{
return pd;
}
}
}
return nullptr;
}
std::string WriteBuffer(vtkCellArray *ca, const char *fileName)
{
std::ostringstream toString;
toString
<< "buffer"
<< ca->GetMTime()
<< ".bin";
std::string fname = toString.str();
std::string fullPath =
vtksys::SystemTools::GetFilenamePath(fileName);
fullPath += "/";
fullPath += fname;
// now write the data
ofstream myFile(fullPath, ios::out | ios::binary);
vtkIdType npts;
vtkIdType *indx;
for (ca->InitTraversal(); ca->GetNextCell(npts,indx); )
{
for (int j = 0; j < npts; ++j)
{
unsigned int value = static_cast<unsigned int>(indx[j]);
myFile.write(reinterpret_cast<char *>(&value), 4);
}
}
myFile.close();
return fname;
}
std::string WriteBuffer(vtkPoints *ca, const char *fileName)
{
std::ostringstream toString;
toString
<< "buffer"
<< ca->GetMTime()
<< ".bin";
std::string fname = toString.str();
std::string fullPath =
vtksys::SystemTools::GetFilenamePath(fileName);
fullPath += "/";
fullPath += fname;
// now write the data
ofstream myFile(fullPath, ios::out | ios::binary);
double pt[3];
float fpt[3];
for (int i = 0; i < ca->GetNumberOfPoints(); ++i)
{
ca->GetPoint(i, pt);
fpt[0] = pt[0];
fpt[1] = pt[1];
fpt[2] = pt[2];
myFile.write(reinterpret_cast<char *>(fpt),12);
}
myFile.close();
return fname;
}
std::string WriteBuffer(vtkUnsignedCharArray *ca, const char *fileName)
{
std::ostringstream toString;
toString
<< "buffer"
<< ca->GetMTime()
<< ".bin";
std::string fname = toString.str();
std::string fullPath =
vtksys::SystemTools::GetFilenamePath(fileName);
fullPath += "/";
fullPath += fname;
// now write the data
ofstream myFile(fullPath, ios::out | ios::binary);
myFile.write(reinterpret_cast<char *>(
ca->GetVoidPointer(0)),
ca->GetNumberOfTuples()*4);
myFile.close();
return fname;
}
// gltf uses hard coded numbers to represent data types
// they match the definitions from gl.h but for your convenience
// some of the common values we use are listed below.
//
// 5121 - unsigned char
// 5125 = unsigned int
// 5126 = float
//
void WriteMesh(
unsigned int &totalAccessors,
Json::Value &accessors,
unsigned int &totalBuffers,
Json::Value &buffers,
unsigned int &totalBufferViews,
Json::Value &bufferViews,
unsigned int &totalMeshes,
Json::Value &meshes,
unsigned int &totalNodes,
Json::Value &nodes,
vtkPolyData *pd,
vtkActor *aPart,
const char *fileName
)
{
vtkNew<vtkTriangleFilter> trif;
trif->SetInputData(pd);
trif->Update();
vtkPolyData *tris = trif->GetOutput();
// write out the mesh
Json::Value aprim;
aprim["mode"] = 4;
Json::Value attribs;
// write the triangles
{
vtkCellArray *da = tris->GetPolys();
std::string fname = WriteBuffer(da, fileName);
Json::Value buffer;
// 12 bytes per tri, one tri per 4 entries
buffer["byteLength"] = static_cast<vtkJson::Value::Int64>(12*da->GetNumberOfCells());
buffer["uri"] = fname;
buffers.append(buffer);
totalBuffers++;
// write the buffer views
Json::Value view;
view["buffer"] = totalBuffers - 1;
view["byteOffset"] = 0;
view["byteLength"] = static_cast<vtkJson::Value::Int64>(12*da->GetNumberOfCells());
bufferViews.append(view);
totalBufferViews++;
// write the accessor
Json::Value acc;
acc["bufferView"] = totalBufferViews -1;
acc["byteOffset"] = 0;
acc["type"] = "SCALAR";
acc["componentType"] = 5125;
acc["count"] = static_cast<vtkJson::Value::Int64>(da->GetNumberOfCells()*3);
accessors.append(acc);
aprim["indices"] = totalAccessors;
totalAccessors++;
}
// write the point locations
{
vtkPoints *da = tris->GetPoints();
std::string fname = WriteBuffer(da, fileName);
Json::Value buffer;
// 3 floats per point
buffer["byteLength"] = static_cast<vtkJson::Value::Int64>(12*da->GetNumberOfPoints());
buffer["uri"] = fname;
buffers.append(buffer);
totalBuffers++;
// write the buffer views
Json::Value view;
view["buffer"] = totalBuffers - 1;
view["byteOffset"] = 0;
view["byteLength"] = static_cast<vtkJson::Value::Int64>(12*da->GetNumberOfPoints());
view["byteStride"] = 12;
bufferViews.append(view);
totalBufferViews++;
// write the accessor
Json::Value acc;
acc["bufferView"] = totalBufferViews -1;
acc["byteOffset"] = 0;
acc["type"] = "VEC3";
acc["componentType"] = 5126;
acc["count"] = static_cast<vtkJson::Value::Int64>(da->GetNumberOfPoints());
double range[6];
da->GetBounds(range);
Json::Value mins;
mins.append(range[0]);
mins.append(range[2]);
mins.append(range[4]);
Json::Value maxs;
maxs.append(range[1]);
maxs.append(range[3]);
maxs.append(range[5]);
acc["min"] = mins;
acc["max"] = maxs;
accessors.append(acc);
attribs["POSITION"] = totalAccessors;
totalAccessors++;
}
// if we have vertex colors then write them out
aPart->GetMapper()->MapScalars(1.0);
if (aPart->GetMapper()->GetColorMapColors())
{
vtkUnsignedCharArray *da = aPart->GetMapper()->GetColorMapColors();
std::string fname = WriteBuffer(da, fileName);
Json::Value buffer;
// 4 uchar per point
buffer["byteLength"] = static_cast<vtkJson::Value::Int64>(4*da->GetNumberOfTuples());
buffer["uri"] = fname;
buffers.append(buffer);
totalBuffers++;
// write the buffer views
Json::Value view;
view["buffer"] = totalBuffers - 1;
view["byteOffset"] = 0;
view["byteLength"] = static_cast<vtkJson::Value::Int64>(4*da->GetNumberOfTuples());
view["byteStride"] = 4;
bufferViews.append(view);
totalBufferViews++;
// write the accessor
Json::Value acc;
acc["bufferView"] = totalBufferViews -1;
acc["byteOffset"] = 0;
acc["type"] = "VEC4";
acc["componentType"] = 5121;
acc["normalized"] = true;
acc["count"] = static_cast<vtkJson::Value::Int64>(da->GetNumberOfTuples());
attribs["COLOR_0"] = totalAccessors;
accessors.append(acc);
totalAccessors++;
}
aprim["attributes"] = attribs;
Json::Value amesh;
Json::Value prims;
prims.append(aprim);
amesh["primitives"] = prims;
meshes.append(amesh);
totalMeshes++;
// write out an actor
Json::Value child;
if (!aPart->GetIsIdentity())
{
vtkMatrix4x4 *amat = aPart->GetMatrix();
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
child["matrix"].append(amat->GetElement(j,i));
}
}
}
child["mesh"] = totalMeshes - 1;
nodes.append(child);
totalNodes++;
}
void WriteCamera(Json::Value &cameras, vtkRenderer *ren)
{
vtkCamera *cam = ren->GetActiveCamera();
Json::Value acamera;
Json::Value camValues;
camValues["znear"] = cam->GetClippingRange()[0];
camValues["zfar"] = cam->GetClippingRange()[1];
if (cam->GetParallelProjection())
{
acamera["type"] = "orthographic";
camValues["xmag"] = cam->GetParallelScale()*ren->GetTiledAspectRatio();
camValues["ymag"] = cam->GetParallelScale();
acamera["orthographic"] = camValues;
}
else
{
acamera["type"] = "perspective";
camValues["yfov"] = cam->GetViewAngle();
camValues["aspectRatio"] = ren->GetTiledAspectRatio();
acamera["perspective"] = camValues;
}
cameras.append(acamera);
}
}
void vtkGLTFExporter::WriteData()
{
ofstream output;
// make sure the user specified a FileName or FilePointer
if (this->FileName == nullptr)
{
vtkErrorMacro(<< "Please specify FileName to use");
return;
}
// try opening the files
output.open(this->FileName);
if (!output.is_open())
{
vtkErrorMacro("Unable to open file for gltf output.");
return;
}
Json::Value cameras;
Json::Value bufferViews;
Json::Value buffers;
Json::Value accessors;
Json::Value nodes;
Json::Value meshes;
std::vector<unsigned int> topNodes;
unsigned int count = 0;
unsigned int totalNodes = 0;
unsigned int totalMeshes = 0;
unsigned int totalBuffers = 0;
unsigned int totalBufferViews = 0;
unsigned int totalAccessors = 0;
for (auto ren : vtk::Range(this->RenderWindow->GetRenderers()))
{
if (this->ActiveRenderer && ren != this->ActiveRenderer)
{
// If ActiveRenderer is specified then ignore all other renderers
continue;
}
if (!ren->GetDraw())
{
continue;
}
WriteCamera(cameras, ren);
Json::Value anode;
anode["camera"] = count; // camera node
vtkMatrix4x4 *mat = ren->GetActiveCamera()->GetModelViewTransformMatrix();
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
anode["matrix"].append(mat->GetElement(j,i));
}
}
anode["name"] = "Camera Node";
vtkPropCollection *pc;
vtkProp *aProp;
pc = ren->GetViewProps();
vtkCollectionSimpleIterator pit;
for (pc->InitTraversal(pit); (aProp = pc->GetNextProp(pit)); )
{
if (!aProp->GetVisibility())
{
continue;
}
vtkNew<vtkActorCollection> ac;
aProp->GetActors(ac);
vtkActor *anActor;
vtkCollectionSimpleIterator ait;
for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait)); )
{
vtkAssemblyPath *apath;
vtkActor *aPart;
for (anActor->InitPathTraversal(); (apath=anActor->GetNextPath()); )
{
aPart = static_cast<vtkActor *>(apath->GetLastNode()->GetViewProp());
if (aPart->GetVisibility() && aPart->GetMapper() && aPart->GetMapper()->GetInputAlgorithm())
{
aPart->GetMapper()->GetInputAlgorithm()->Update();
vtkPolyData *pd = findPolyData(aPart->GetMapper()->GetInputDataObject(0,0));
if (pd && pd->GetPolys() && pd->GetNumberOfCells() > 0)
{
WriteMesh(
totalAccessors, accessors,
totalBuffers, buffers,
totalBufferViews, bufferViews,
totalMeshes, meshes,
totalNodes, nodes,
pd, aPart, this->FileName);