Commit 3bbf5d2b authored by Christian Godenschwager's avatar Christian Godenschwager
Browse files

Added mesh module

parent 25f35ef4
Pipeline #6342 passed with stage
in 114 minutes and 47 seconds
......@@ -930,6 +930,9 @@ if( (NOT DEFINED WALBERLA_BUILD_WITH_OPENMESH) OR WALBERLA_BUILD_WITH_OPENMESH )
set( WALBERLA_BUILD_WITH_OPENMESH ON CACHE BOOL "Build with OpenMesh support" )
include_directories( SYSTEM ${OPENMESH_INCLUDE_DIRS} )
list( APPEND SERVICE_LIBS ${OPENMESH_LIBRARIES} )
if( WALBERLA_CXX_COMPILER_IS_MSVC )
add_definitions(-D_USE_MATH_DEFINES )
endif()
else()
set( WALBERLA_BUILD_WITH_OPENMESH OFF CACHE BOOL "Build with OpenMesh support" FORCE )
endif()
......
add_subdirectory( ComplexGeometry )
add_subdirectory( MeshDistance )
add_subdirectory( CouetteFlow )
add_subdirectory( ForcesOnSphereNearPlaneInShearFlow )
add_subdirectory( NonUniformGrid )
......
if ( WALBERLA_BUILD_WITH_OPENMESH )
waLBerla_link_files_to_builddir( "*.obj" )
waLBerla_link_files_to_builddir( "*.conf" )
waLBerla_add_executable( NAME ComplexGeometry FILES ComplexGeometry.cpp DEPENDS boundary core lbm mesh vtk )
##############
# Some tests #
##############
waLBerla_execute_test( NO_MODULE_LABEL NAME ComplexGeometry COMMAND $<TARGET_FILE:ComplexGeometry> test.conf DEPENDS_ON_TARGETS ComplexGeometry )
endif()
//======================================================================================================================
//
// This file is part of waLBerla. waLBerla is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// waLBerla is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file ComplexGeometry.cpp
//! \ingroup mesh
//! \author Christian Godenschwager <christian.godenschwager@fau.de>
//
//======================================================================================================================
#include "blockforest/Initialization.h"
#include "blockforest/SetupBlockForest.h"
#include "blockforest/communication/UniformBufferedScheme.h"
#include "blockforest/loadbalancing/StaticParMetis.h"
#include "core/SharedFunctor.h"
#include "core/Environment.h"
#include "core/logging/Logging.h"
#include "core/math/IntegerFactorization.h"
#include "domain_decomposition/SharedSweep.h"
#include "field/AddToStorage.h"
#include "field/StabilityChecker.h"
#include "lbm/boundary/factories/DefaultBoundaryHandling.h"
#include "lbm/communication/PdfFieldPackInfo.h"
#include "lbm/communication/SparsePdfFieldPackInfo.h"
#include "lbm/field/AddToStorage.h"
#include "lbm/lattice_model/D3Q19.h"
#include "lbm/lattice_model/D3Q27.h"
#include "lbm/lattice_model/CollisionModel.h"
#include "lbm/lattice_model/ForceModel.h"
#include "lbm/refinement/TimeStep.h"
#include "lbm/sweeps/CellwiseSweep.h"
#include "lbm/sweeps/SplitPureSweep.h"
#include "lbm/vtk/VTKOutput.h"
#include "lbm/BlockForestEvaluation.h"
#include "lbm/PerformanceEvaluation.h"
#include "lbm/PerformanceLogger.h"
#include "geometry/mesh/TriangleMesh.h"
#include "geometry/mesh/TriangleMeshIO.h"
#include "geometry/InitBoundaryHandling.h"
#include "mesh/TriangleMeshes.h"
#include "mesh/MeshOperations.h"
#include "mesh/DistanceComputations.h"
#include "mesh/MeshIO.h"
#include "mesh/MatrixVectorOperations.h"
#include "mesh/blockforest/BlockForestInitialization.h"
#include "mesh/blockforest/BlockWorkloadMemory.h"
#include "mesh/blockforest/BlockExclusion.h"
#include "mesh/blockforest/RefinementSelection.h"
#include "mesh/distance_octree/DistanceOctree.h"
#include "mesh/boundary/BoundarySetup.h"
#include "mesh/boundary/BoundaryInfo.h"
#include "mesh/boundary/BoundaryLocation.h"
#include "mesh/boundary/BoundaryUIDFaceDataSource.h"
#include "mesh/boundary/ColorToBoundaryMapper.h"
#include "mesh/vtk/VTKMeshWriter.h"
#include "mesh/vtk/CommonDataSources.h"
#include "stencil/D3Q19.h"
#include "timeloop/SweepTimeloop.h"
#include "core/timing/RemainingTimeLogger.h"
#include <boost/lexical_cast.hpp>
#include <cmath>
#include <vector>
#include <string>
namespace walberla {
template< typename MeshDistanceType >
struct MeshDistanceFunction
{
MeshDistanceFunction( const shared_ptr< MeshDistanceType > & meshDistanceObject ) : meshDistanceObject_( meshDistanceObject ) { }
inline real_t operator()( const Vector3< real_t > & p ) const { return real_c( meshDistanceObject_->sqSignedDistance( mesh::toOpenMesh( p ) ) ); }
shared_ptr< MeshDistanceType > meshDistanceObject_;
};
template< typename MeshDistanceType >
inline MeshDistanceFunction< MeshDistanceType > makeMeshDistanceFunction( const shared_ptr< MeshDistanceType > & meshDistanceObject )
{
return MeshDistanceFunction< MeshDistanceType >( meshDistanceObject );
}
template< typename MeshDistanceType, typename MeshType >
struct BoundaryLocationFunction
{
BoundaryLocationFunction( const shared_ptr< MeshDistanceType > & meshDistanceObject, const shared_ptr< mesh::BoundaryLocation< MeshType > > & boundaryLocation )
: meshDistanceObject_( meshDistanceObject ), boundaryLocation_( boundaryLocation ) { }
inline const mesh::BoundaryInfo & operator()( const Vector3< real_t > & p ) const
{
typename MeshType::FaceHandle fh;
meshDistanceObject_->sqSignedDistance( mesh::toOpenMesh( p ), fh );
return (*boundaryLocation_)[ fh ];
}
shared_ptr< MeshDistanceType > meshDistanceObject_;
shared_ptr< mesh::BoundaryLocation< MeshType > > boundaryLocation_;
};
template< typename MeshDistanceType, typename MeshType >
inline BoundaryLocationFunction< MeshDistanceType, MeshType > makeBoundaryLocationFunction( const shared_ptr< MeshDistanceType > & meshDistanceObject, const shared_ptr< mesh::BoundaryLocation< MeshType > > & boundaryLocation )
{
return BoundaryLocationFunction< MeshDistanceType, MeshType >( meshDistanceObject, boundaryLocation );
}
template< typename MeshType >
void vertexToFaceColor( MeshType & mesh, const typename MeshType::Color & defaultColor )
{
WALBERLA_CHECK( mesh.has_vertex_colors() );
mesh.request_face_colors();
for( auto faceIt = mesh.faces_begin(); faceIt != mesh.faces_end(); ++faceIt )
{
typename MeshType::Color vertexColor;
bool useVertexColor = true;
auto vertexIt = mesh.fv_iter( *faceIt );
WALBERLA_ASSERT( vertexIt.is_valid() );
vertexColor = mesh.color( *vertexIt );
++vertexIt;
while( vertexIt.is_valid() && useVertexColor )
{
if( vertexColor != mesh.color( *vertexIt ) )
useVertexColor = false;
++vertexIt;
}
mesh.set_color( *faceIt, useVertexColor ? vertexColor : defaultColor );
}
}
int main( int argc, char * argv[] )
{
Environment env( argc, argv );
if( !env.config() )
{
WALBERLA_ABORT_NO_DEBUG_INFO( "USAGE: " << argv[0] << " INPUT_FILE" );
}
mpi::MPIManager::instance()->useWorldComm();
const auto & config = *( env.config() );
Config::BlockHandle configBlock = config.getOneBlock( "ComplexGeometry" );
const std::string meshFile = configBlock.getParameter< std::string >( "meshFile" );
const real_t dx = configBlock.getParameter< real_t >( "coarseDx" );
const real_t omega = configBlock.getParameter< real_t >( "coarseOmega" );
//const uint_t blockPerProcess = configBlock.getParameter< uint_t >( "blocksPerProcess", uint_t(6) );
const uint_t timeSteps = configBlock.getParameter< uint_t >( "coarseTimeSteps" );
const Vector3<real_t> bodyForce = configBlock.getParameter< Vector3<real_t> >( "bodyForce" );
//const bool sparseCommunication = configBlock.getParameter< bool >( "sparseCommunication", true );
const Vector3<real_t> domainBlowUp = configBlock.getParameter< Vector3<real_t> >( "domainBlowUp", Vector3<real_t>(6) );
const Vector3<uint_t> blockSize = configBlock.getParameter< Vector3<uint_t> >( "blockSize", Vector3<uint_t>(16) );
uint_t numLevels = configBlock.getParameter< uint_t >( "numLevels", uint_t(2) );
numLevels = std::max( numLevels, uint_t(1) );
//uint_t numProcesses = uint_c( MPIManager::instance()->numProcesses() );
WALBERLA_LOG_DEVEL_VAR_ON_ROOT( meshFile );
auto mesh = make_shared< mesh::TriangleMesh >();
mesh->request_vertex_colors();
WALBERLA_LOG_DEVEL_ON_ROOT( "Loading mesh" );
mesh::readAndBroadcast( meshFile, *mesh);
vertexToFaceColor( *mesh, mesh::TriangleMesh::Color(255,255,255) );
WALBERLA_LOG_DEVEL_ON_ROOT( "Adding distance info to mesh" );
auto triDist = make_shared< mesh::TriangleDistance<mesh::TriangleMesh> >( mesh );
WALBERLA_LOG_DEVEL_ON_ROOT( "Building distance octree" );
auto distanceOctree = make_shared< mesh::DistanceOctree<mesh::TriangleMesh> >( triDist );
WALBERLA_LOG_DEVEL_ON_ROOT( "done. Octree has height " << distanceOctree->height() );
distanceOctree->writeVTKOutput("distanceOctree");
auto aabb = computeAABB( *mesh );
aabb.scale( domainBlowUp );
mesh::ComplexGeometryStructuredBlockforestCreator bfc( aabb, Vector3<real_t>( dx ), mesh::makeExcludeMeshInterior( distanceOctree, dx ) );
auto meshWorkloadMemory = mesh::makeMeshWorkloadMemory( distanceOctree, dx );
meshWorkloadMemory.setInsideCellWorkload(1);
meshWorkloadMemory.setOutsideCellWorkload(1);
bfc.setWorkloadMemorySUIDAssignmentFunction( meshWorkloadMemory );
bfc.setPeriodicity( Vector3<bool>(true) );
bfc.setRefinementSelectionFunction( makeRefinementSelection( distanceOctree, numLevels - uint_t(1), dx, dx * real_t(1) ) );
auto structuredBlockforest = bfc.createStructuredBlockForest( blockSize );
typedef lbm::D3Q19<lbm::collision_model::SRT, false, lbm::force_model::SimpleConstant> LatticeModel_T;
typedef walberla::uint8_t flag_t;
typedef FlagField< flag_t > FlagField_T;
typedef lbm::PdfField< LatticeModel_T > PdfField_T;
LatticeModel_T latticeModel{ lbm::collision_model::SRT( omega ), lbm::force_model::SimpleConstant( bodyForce ) };
static const uint_t NUM_GHOSTLAYERS = 4;
BlockDataID pdfFieldId = lbm::addPdfFieldToStorage( structuredBlockforest, "pdf field", latticeModel, Vector3<real_t>(0), real_t(1), NUM_GHOSTLAYERS, field::fzyx );
BlockDataID flagFieldId = field::addFlagFieldToStorage< FlagField_T >( structuredBlockforest, "flag field", NUM_GHOSTLAYERS );
const FlagUID fluidFlagUID( "Fluid" );
typedef lbm::DefaultBoundaryHandlingFactory< LatticeModel_T, FlagField_T > BHFactory;
auto boundariesConfig = configBlock.getOneBlock( "Boundaries" );
BlockDataID boundaryHandlingId = BHFactory::addBoundaryHandlingToStorage( structuredBlockforest, "boundary handling", flagFieldId, pdfFieldId, fluidFlagUID,
boundariesConfig.getParameter< Vector3<real_t> >( "velocity0", Vector3<real_t>() ),
boundariesConfig.getParameter< Vector3<real_t> >( "velocity1", Vector3<real_t>() ),
boundariesConfig.getParameter< real_t > ( "pressure0", real_c( 1.0 ) ),
boundariesConfig.getParameter< real_t > ( "pressure1", real_c( 1.001 ) ) );
mesh::ColorToBoundaryMapper<mesh::TriangleMesh> colorToBoundryMapper(( mesh::BoundaryInfo( BHFactory::getNoSlipBoundaryUID() ) ));
// colorToBoundryMapper.set( mesh::TriangleMesh::Color(255,0,0), mesh::BoundaryInfo( BHFactory::getPressure0BoundaryUID() ) );
// colorToBoundryMapper.set( mesh::TriangleMesh::Color(0,0,255), mesh::BoundaryInfo( BHFactory::getPressure1BoundaryUID() ) );
// colorToBoundryMapper.set( mesh::TriangleMesh::Color(255,255,255), mesh::BoundaryInfo( BHFactory::getNoSlipBoundaryUID() ) );
auto boundaryLocations = colorToBoundryMapper.addBoundaryInfoToMesh( *mesh );
mesh::VTKMeshWriter< mesh::TriangleMesh > meshWriter( mesh, "meshBoundaries", 1 );
meshWriter.addDataSource( make_shared< mesh::BoundaryUIDFaceDataSource< mesh::TriangleMesh > >( boundaryLocations ) );
meshWriter.addDataSource( make_shared< mesh::ColorFaceDataSource< mesh::TriangleMesh > >() );
meshWriter.addDataSource( make_shared< mesh::ColorVertexDataSource< mesh::TriangleMesh > >() );
meshWriter();
WALBERLA_LOG_DEVEL_ON_ROOT( "Voxelizing mesh" );
mesh::BoundarySetup boundarySetup( structuredBlockforest, makeMeshDistanceFunction( distanceOctree ), NUM_GHOSTLAYERS );
//WALBERLA_LOG_DEVEL( "Writing Voxelisation" );
//boundarySetup.writeVTKVoxelfile();
WALBERLA_LOG_DEVEL_ON_ROOT( "Setting up fluid cells" );
boundarySetup.setDomainCells<BHFactory::BoundaryHandling>( boundaryHandlingId, mesh::BoundarySetup::OUTSIDE );
WALBERLA_LOG_DEVEL_ON_ROOT( "Setting up boundaries" );
boundarySetup.setBoundaries<BHFactory::BoundaryHandling>( boundaryHandlingId, makeBoundaryLocationFunction( distanceOctree, boundaryLocations ), mesh::BoundarySetup::INSIDE );
WALBERLA_LOG_DEVEL_ON_ROOT( "done" );
lbm::BlockForestEvaluation<FlagField_T>( structuredBlockforest, flagFieldId, fluidFlagUID ).logInfoOnRoot();
lbm::PerformanceLogger<FlagField_T> perfLogger( structuredBlockforest, flagFieldId, fluidFlagUID, 100 );
SweepTimeloop timeloop( structuredBlockforest->getBlockStorage(), timeSteps );
auto sweep = lbm::makeCellwiseSweep< LatticeModel_T, FlagField_T >( pdfFieldId, flagFieldId, fluidFlagUID );
auto refinementTimeStep = lbm::refinement::makeTimeStep<LatticeModel_T, BHFactory::BoundaryHandling > ( structuredBlockforest, sweep, pdfFieldId, boundaryHandlingId );
timeloop.addFuncBeforeTimeStep( makeSharedFunctor( refinementTimeStep ), "Refinement time step" );
// log remaining time
timeloop.addFuncAfterTimeStep( timing::RemainingTimeLogger( timeloop.getNrOfTimeSteps() ), "remaining time logger" );
// LBM stability check
timeloop.addFuncAfterTimeStep( makeSharedFunctor( field::makeStabilityChecker< PdfField_T, FlagField_T >( env.config(), structuredBlockforest, pdfFieldId,
flagFieldId, fluidFlagUID ) ),
"LBM stability check" );
timeloop.addFuncAfterTimeStep( perfLogger, "PerformanceLogger" );
// add VTK output to time loop
lbm::VTKOutput< LatticeModel_T, FlagField_T >::addToTimeloop( timeloop, structuredBlockforest, env.config(), pdfFieldId, flagFieldId, fluidFlagUID );
WcTimingPool timingPool;
WALBERLA_LOG_INFO_ON_ROOT( "Starting timeloop" );
timeloop.run( timingPool );
WALBERLA_LOG_INFO_ON_ROOT( "Timeloop done" );
timingPool.unifyRegisteredTimersAcrossProcesses();
timingPool.logResultOnRoot( WcTimingPool::REDUCE_TOTAL, true );
return EXIT_SUCCESS;
}
} // namespace walberla
int main( int argc, char * argv[] )
{
return walberla::main( argc, argv );
}
\ No newline at end of file
This diff is collapsed.
####
#
# OBJ File Generated by Meshlab
#
####
# Object cube.obj
#
# Vertices: 8
# Faces: 12
#
####
v 0.000000 0.000000 0.000000
v 0.000000 0.000000 1.000000
v 0.000000 1.000000 1.000000
v 0.000000 1.000000 0.000000
v 1.000000 0.000000 0.000000
v 1.000000 0.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 1.000000 0.000000
# 8 vertices, 0 vertices normals
f 1 2 3
f 1 3 4
f 8 7 6
f 8 6 5
f 1 5 6
f 1 6 2
f 2 6 7
f 2 7 3
f 3 7 8
f 3 8 4
f 4 8 5
f 4 5 1
# 12 faces, 0 coords texture
# End of File
\ No newline at end of file
ComplexGeometry
{
meshFile bunny.obj;
coarseDx 4;
coarseOmega 1.6;
coarseTimeSteps 1001;
numLevels 3;
bodyForce <0.0001, 0, 0>;
blockSize <16,16,16>;
domainBlowUp <5,5,5>; // simulation domain is blow up factor times mesh size per dimension
}
VTK {
fluid // identifier for this VTKOutput object
{
writeFrequency 100;
inclusion_filters
{
DomainFilter;
}
writers
{
Velocity;
Density;
}
}
flags // identifier for this VTKOutput object
{
writeFrequency 999999999999999; //once!
writers
{
FlagField;
}
}
domain_decomposition // identifier for this VTKOutput object
{
writeFrequency 999999999999999; //once!
outputDomainDecomposition true;
}
}
\ No newline at end of file
####
#
# OBJ File Generated by Meshlab
#
####
# Object sphere.obj
#
# Vertices: 52
# Faces: 100
#
####
vn 3.203486 0.106207 4.556782
v 0.589717 0.013032 0.847959 0.752941 0.752941 0.752941
vn 5.019849 1.775524 1.684732
v 0.919162 0.323653 0.317881 0.752941 0.752941 0.752941
vn 2.435736 2.603171 4.338618
v 0.454446 0.492016 0.779993 0.752941 0.752941 0.752941
vn 1.166519 4.432512 3.718873
v 0.245629 0.761303 0.617920 0.752941 0.752941 0.752941
vn 3.325176 4.334585 2.054782
v 0.568591 0.771892 0.343297 0.752941 0.752941 0.752941
vn 0.595211 5.541665 1.103712
v 0.110726 0.991360 0.237197 0.752941 0.752941 0.752941
vn -3.776296 0.767626 4.266579
v -0.688569 0.139508 0.738187 0.000000 1.000000 0.000000
vn -1.312800 2.808851 4.706018
v -0.270132 0.525142 0.837286 0.000000 1.000000 0.000000
vn -3.673248 3.342919 3.246579
v -0.616965 0.568913 0.559955 0.000000 1.000000 0.000000
vn -5.184217 1.765921 1.597865
v -0.913888 0.321159 0.322155 0.000000 1.000000 0.000000
vn -1.576481 4.921260 2.246386
v -0.288050 0.891505 0.427122 0.000000 1.000000 0.000000
vn -3.631493 4.235586 0.210513
v -0.677783 0.771802 0.054808 0.000000 1.000000 0.000000
vn 5.347003 -0.730677 2.243891
v 0.939495 -0.137963 0.353373 0.752941 0.752941 0.752941
vn 3.090607 -2.164615 4.376353
v 0.564054 -0.405378 0.749705 0.752941 0.752941 0.752941
vn 4.139648 -3.083573 2.833979
v 0.686938 -0.535204 0.519371 0.752941 0.752941 0.752941
vn 5.547247 -1.363092 0.328926
v 0.988626 -0.269261 0.045694 0.752941 0.752941 0.752941
vn 1.196453 -3.595379 4.241168
v 0.225855 -0.666498 0.743600 0.752941 0.752941 0.752941
vn 3.440788 -4.400445 1.622125
v 0.605470 -0.764079 0.286901 0.752941 0.752941 0.752941
vn 1.483118 -5.448192 -0.612228
v 0.268689 -0.989603 -0.074576 0.752941 0.752941 0.752941
vn -5.131380 -1.139949 2.118813
v -0.938366 -0.205491 0.365196 0.000000 1.000000 0.000000
vn -0.148252 -0.090956 5.329934
v -0.029807 -0.012393 1.044330 0.752941 0.752941 0.752941
vn -2.941041 -1.873027 4.366208
v -0.544021 -0.347070 0.802954 0.752941 0.752941 0.752941
vn -4.510469 -3.611316 0.273923
v -0.799559 -0.630144 0.056333 0.752941 0.752941 0.752941
vn -1.222675 -3.813472 4.059360
v -0.223449 -0.686099 0.721907 0.752941 0.752941 0.752941
vn -3.177381 -4.314878 2.055289
v -0.568591 -0.771892 0.343297 0.752941 0.752941 0.752941
vn 0.555067 -5.357186 2.097567
v 0.103775 -0.959491 0.337408 0.752941 0.752941 0.752941
vn -1.424179 -5.441336 0.261796
v -0.242174 -0.999619 0.061062 0.752941 0.752941 0.752941
vn 0.603496 0.876270 -5.566095
v 0.094800 0.183733 -1.001620 0.752941 0.752941 0.752941
vn 3.764494 0.845602 -4.219594
v 0.688569 0.139508 -0.738187 0.752941 0.752941 0.752941
vn 1.567199 2.909075 -4.657190
v 0.270132 0.525142 -0.837286 0.752941 0.752941 0.752941
vn 3.565448 3.772605 -2.549637
v 0.615553 0.702721 -0.424646 0.752941 0.752941 0.752941
vn 5.162828 1.763767 -1.614322
v 0.913888 0.321159 -0.322155 0.752941 0.752941 0.752941
vn 1.324314 5.031288 -2.331577
v 0.253180 0.897558 -0.437313 0.752941 0.752941 0.752941
vn 3.629731 4.384550 -0.093890
v 0.650335 0.795689 -0.024263 0.752941 0.752941 0.752941
vn -4.043519 0.309797 -4.155685
v -0.705153 0.056764 -0.729210 0.000000 1.000000 0.000000
vn -2.307409 1.462065 -5.138504
v -0.415375 0.253185 -0.890210 0.752941 0.752941 0.752941
vn -3.353315 3.567168 -2.854749
v -0.600441 0.642788 -0.533812 0.000000 1.000000 0.000000
vn -5.130730 1.780599 -1.582083
v -0.919162 0.323653 -0.317881 0.000000 1.000000 0.000000
vn -1.144898 3.843270 -4.171122
v -0.223449 0.686099 -0.721907 0.752941 0.752941 0.752941
vn -0.991535 5.441091 -1.454840
v -0.143287 0.963273 -0.301638 0.000000 1.000000 0.000000
vn 5.131380 -1.139949 -2.118813
v 0.938366 -0.205491 -0.365196 0.752941 0.752941 0.752941
vn 2.875490 -1.819046 -4.391660
v 0.544021 -0.347070 -0.802954 0.752941 0.752941 0.752941
vn 4.525778 -3.601130 -0.274730
v 0.799559 -0.630144 -0.056333 0.752941 0.752941 0.752941
vn 1.250127 -3.837570 -4.049940
v 0.223449 -0.686099 -0.721907 0.752941 0.752941 0.752941
vn 3.198936 -4.303796 -2.086038
v 0.568591 -0.771892 -0.343297 0.752941 0.752941 0.752941
vn -0.857578 -1.469198 -5.294847
v -0.187308 -0.267522 -0.979869 0.752941 0.752941 0.752941
vn -3.158328 -2.247927 -4.280213
v -0.564054 -0.405378 -0.749705 0.752941 0.752941 0.752941
vn -5.397189 -0.800014 -2.176419
v -0.939495 -0.137963 -0.353373 0.000000 1.000000 0.000000
vn -4.099356 -3.088730 -2.858676
v -0.686938 -0.535204 -0.519371 0.752941 0.752941 0.752941
vn -5.547246 -1.363092 -0.328926
v -0.988626 -0.269261 -0.045694 0.000000 1.000000 0.000000
vn -3.407609 -4.424288 -1.578537
v -0.605470 -0.764079 -0.286901 0.752941 0.752941 0.752941
vn -0.843019 -4.558556 -3.151353
v -0.153857 -0.826487 -0.592781 0.752941 0.752941 0.752941
# 52 vertices, 0 vertices normals
f 16//16 2//2 13//13
f 16//16 32//32 2//2
f 2//2 3//3 1//1
f 1//1 3//3 21//21
f 21//21 3//3 8//8
f 2//2 5//5 3//3
f 2//2 34//34 5//5
f 3//3 5//5 4//4
f 3//3 4//4 8//8
f 5//5 6//6 4//4
f 4//4 6//6 11//11
f 5//5 34//34 6//6
f 34//34 33//33 6//6
f 22//22 21//21 7//7
f 7//7 10//10 20//20
f 20//20 10//10 50//50
f 7//7 21//21 8//8
f 7//7 8//8 9//9
f 7//7 9//9 10//10
f 10//10 9//9 12//12
f 10//10 12//12 38//38
f 8//8 11//11 9//9
f 9//9 11//11 12//12
f 8//8 4//4 11//11
f 1//1 13//13 2//2
f 1//1 14//14 13//13
f 1//1 21//21 14//14
f 14//14 15//15 13//13
f 13//13 15//15 16//16
f 21//21 17//17 14//14
f 15//15 18//18 16//16
f 16//16 18//18 43//43
f 14//14 17//17 15//15
f 15//15 17//17 18//18
f 17//17 24//24 26//26
f 17//17 26//26 18//18
f 18//18 19//19 43//43
f 18//18 26//26 19//19
f 7//7 20//20 22//22
f 50//50 23//23 20//20
f 22//22 24//24 21//21
f 21//21 24//24 17//17
f 23//23 25//25 20//20
f 20//20 25//25 22//22
f 22//22 25//25 24//24
f 23//23 27//27 25//25
f 25//25 27//27 24//24
f 24//24 27//27 26//26
f 42//42 28//28 29//29
f 29//29 32//32 41//41
f 41//41 32//32 16//16
f 29//29 28//28 30//30
f 29//29 30//30 31//31
f 29//29 31//31 32//32