// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// clang-format off
#include "stdafx.h"
// clang-format on

#include "gtest/gtest.h"

#include <frantic/geometry/mesh_interface_utils.hpp>
#include <frantic/geometry/polymesh3.hpp>
#include <frantic/geometry/polymesh3_builder.hpp>
#include <frantic/geometry/polymesh3_interface.hpp>
#include <frantic/geometry/trimesh3.hpp>
#include <frantic/geometry/trimesh3_interface.hpp>

#include <frantic/particles/particle_array.hpp>

#include "utilities/mesh_generators.hpp"

TEST( MeshInterfaceUtils, ComputeBoundboxEmpty ) {
    frantic::geometry::trimesh3 mesh;

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    frantic::geometry::boundbox3f bounds = frantic::geometry::compute_boundbox( i );

    ASSERT_TRUE( bounds.is_empty() );
}

TEST( MeshInterfaceUtils, ComputeBoundboxUnitCube ) {
    frantic::geometry::trimesh3 mesh;
    mesh.set_to_box( frantic::graphics::boundbox3f( 0, 1, 0, 1, 0, 1 ) );

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    frantic::geometry::boundbox3f bounds = frantic::geometry::compute_boundbox( i );

    ASSERT_EQ( bounds, frantic::graphics::boundbox3f( 0, 1, 0, 1, 0, 1 ) );
}

TEST( MeshInterfaceUtils, GetFaceVertexCount ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 1, 0, 0 );
    mesh.add_vertex( 1, 1, 0 );
    mesh.add_vertex( 0, 1, 0 );

    mesh.add_face( 0, 1, 2 );
    mesh.add_face( 0, 2, 3 );

    frantic::geometry::mesh_interface::ptr_type m(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    EXPECT_EQ( frantic::geometry::get_face_vertex_count( m.get() ), 6 );
}

TEST( MeshInterfaceUtils, IsClosedManifoldOneTriangle ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 1, 0, 0 );
    mesh.add_vertex( 1, 1, 0 );

    mesh.add_face( 0, 1, 2 );

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    EXPECT_FALSE( frantic::geometry::is_closed_manifold( i ) );
}

TEST( MeshInterfaceUtils, IsClosedManifoldUnitCube ) {
    frantic::geometry::trimesh3 mesh;
    mesh.set_to_box( frantic::graphics::boundbox3f( 0, 1, 0, 1, 0, 1 ) );

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    EXPECT_TRUE( frantic::geometry::is_closed_manifold( i ) );
}

TEST( MeshInterfaceUtils, IsClosedManifoldOneSingularEdge ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 0, 0, 1 );
    mesh.add_vertex( -1, 0, 0 );
    mesh.add_vertex( 0, 1, 0 );
    mesh.add_vertex( 1, 0, 0 );

    mesh.add_face( 0, 1, 2 );
    mesh.add_face( 0, 1, 3 );
    mesh.add_face( 0, 1, 4 );

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    EXPECT_FALSE( frantic::geometry::is_closed_manifold( i ) );
}

TEST( MeshInterfaceUtils, HasDegenerateFaceNormalTriangle ) {
    using namespace frantic::geometry;

    polymesh3_ptr mesh = make_triangle_polymesh();
    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );

    EXPECT_FALSE( has_degenerate_faces( meshInterface ) );
}

TEST( MeshInterfaceUtils, HasDegenerateFaceSliverTriangle ) {
    using namespace frantic::geometry;

    polymesh3_ptr mesh = make_sliver_polymesh();
    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );

    EXPECT_TRUE( has_degenerate_faces( meshInterface ) );
}

TEST( MeshInterfaceUtils, FixDegenerateFaceSliverTriangle ) {
    using namespace frantic::geometry;

    polymesh3_ptr mesh = make_sliver_polymesh();
    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    EXPECT_EQ( 0, fixed->face_count() );
}

TEST( MeshInterfaceUtils, FixDegenerateFaceDuplicateCorner ) {
    using namespace frantic::geometry;

    polymesh3_builder builder;
    builder.add_vertex( 0, 0, 0 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, 1, 0 );
    const int points[] = { 0, 1, 1, 2 };
    builder.add_polygon( points, 4 );
    polymesh3_ptr mesh = builder.finalize();

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    ASSERT_EQ( 1, fixed->face_count() );
    polymesh3_const_vertex_accessor<void> geomAcc = fixed->get_const_vertex_accessor( _T("verts") );

    polymesh3_const_vertex_accessor<void>::const_face_range face = geomAcc.get_face( 0 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 0, face.first[0] );
    EXPECT_EQ( 1, face.first[1] );
    EXPECT_EQ( 2, face.first[2] );
}

TEST( MeshInterfaceUtils, FixDegenerateFacePinchedPolygon ) {
    using namespace frantic::geometry;

    polymesh3_builder builder;
    builder.add_vertex( 0, 0, 0 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, 0, 2 );
    builder.add_vertex( 0, 1, 2 );
    builder.add_vertex( 0, 1, 0 );
    const int points[] = { 0, 1, 2, 3, 1, 4 };
    builder.add_polygon( points, 6 );
    polymesh3_ptr mesh = builder.finalize();

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    ASSERT_EQ( 2, fixed->face_count() );
    polymesh3_const_vertex_accessor<void> geomAcc = fixed->get_const_vertex_accessor( _T("verts") );

    polymesh3_const_vertex_accessor<void>::const_face_range face = geomAcc.get_face( 0 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 1, face.first[0] );
    EXPECT_EQ( 2, face.first[1] );
    EXPECT_EQ( 3, face.first[2] );

    face = geomAcc.get_face( 1 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 0, face.first[0] );
    EXPECT_EQ( 1, face.first[1] );
    EXPECT_EQ( 4, face.first[2] );
}

TEST( MeshInterfaceUtils, FixDegenerateFacePinchedSliver ) {
    using namespace frantic::geometry;

    polymesh3_builder builder;
    builder.add_vertex( 0, 0, 0 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, 0, 2 );
    builder.add_vertex( 0, 1, 0 );
    const int points[] = { 0, 1, 2, 1, 3 };
    builder.add_polygon( points, 5 );
    polymesh3_ptr mesh = builder.finalize();

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    ASSERT_EQ( 1, fixed->face_count() );
    polymesh3_const_vertex_accessor<void> geomAcc = fixed->get_const_vertex_accessor( _T("verts") );

    polymesh3_const_vertex_accessor<void>::const_face_range face = geomAcc.get_face( 0 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 0, face.first[0] );
    EXPECT_EQ( 1, face.first[1] );
    EXPECT_EQ( 3, face.first[2] );
}

TEST( MeshInterfaceUtils, FixDegenerateFaceVertexChannel ) {
    using namespace frantic::channels;
    using namespace frantic::geometry;
    using namespace frantic::graphics;

    polymesh3_builder builder;
    builder.add_vertex( 0, 0, 0 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, 0, 2 );
    builder.add_vertex( 0, 1, 2 );
    builder.add_vertex( 0, 1, 0 );
    const int points[] = { 0, 1, 2, 3, 1, 4 };
    builder.add_polygon( points, 6 );
    polymesh3_ptr mesh = builder.finalize();

    float data[] = { 5, 6, 7, 8 };
    frantic::graphics::raw_byte_buffer buffer( data, sizeof( data ) );

    std::vector<int> polygon;
    polygon.push_back( 2 );
    polygon.push_back( 1 );
    polygon.push_back( 0 );
    polygon.push_back( 1 );
    polygon.push_back( 3 );
    polygon.push_back( 2 );

    mesh->add_vertex_channel( _T("Channel"), data_type_float32, 1, buffer, &polygon );

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    polymesh3_const_vertex_accessor<void> chanAcc = fixed->get_const_vertex_accessor( _T("Channel") );
    ASSERT_EQ( 2, chanAcc.face_count() );

    polymesh3_const_vertex_accessor<void>::const_face_range face = chanAcc.get_face( 0 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 1, face.first[0] );
    EXPECT_EQ( 0, face.first[1] );
    EXPECT_EQ( 1, face.first[2] );

    face = chanAcc.get_face( 1 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 2, face.first[0] );
    EXPECT_EQ( 3, face.first[1] );
    EXPECT_EQ( 2, face.first[2] );
}

TEST( MeshInterfaceUtils, FixDegenerateFaceFaceChannel ) {
    using namespace frantic::channels;
    using namespace frantic::geometry;
    using namespace frantic::graphics;

    polymesh3_builder builder;
    builder.add_vertex( 0, 0, 0 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, 0, 2 );
    builder.add_vertex( 0, 1, 2 );
    builder.add_vertex( 0, 1, 0 );
    const int points[] = { 0, 1, 2, 3, 1, 4 };
    builder.add_polygon( points, 6 );
    polymesh3_ptr mesh = builder.finalize();

    float data[] = { 5 };
    frantic::graphics::raw_byte_buffer buffer( data, sizeof( data ) );
    mesh->add_face_channel( _T("Channel"), data_type_float32, 1, buffer );

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    polymesh3_face_accessor<void> chanAcc = fixed->get_face_accessor( _T("Channel") );
    ASSERT_EQ( 2, chanAcc.face_count() );

    const float* face = reinterpret_cast<float*>( chanAcc.get_face( 0 ) );
    EXPECT_EQ( 5, *face );

    face = reinterpret_cast<float*>( chanAcc.get_face( 1 ) );
    EXPECT_EQ( 5, *face );
}

TEST( MeshInterfaceUtils, FixDegenerateFaceMultiplePolygons ) {
    using namespace frantic::channels;
    using namespace frantic::geometry;
    using namespace frantic::graphics;

    polymesh3_builder builder;
    builder.add_vertex( 0, -2, 1 );
    builder.add_vertex( 0, 0, 1 );
    builder.add_vertex( 0, -1, 0 );
    builder.add_vertex( 0, -2, 0 );
    builder.add_vertex( 0, 2, 1 );
    builder.add_vertex( 0, 2, 0 );
    builder.add_vertex( 0, 1, 0 );

    const int first[4] = { 0, 1, 2, 3 };
    builder.add_polygon( first, 4 );
    const int second[4] = { 2, 1, 1, 6 };
    builder.add_polygon( second, 4 );
    const int third[4] = { 1, 4, 5, 6 };
    builder.add_polygon( third, 4 );

    polymesh3_ptr mesh = builder.finalize();

    float data[] = { 5, 6, 7 };
    frantic::graphics::raw_byte_buffer buffer( data, sizeof( data ) );
    mesh->add_face_channel( _T("Channel"), data_type_float32, 1, buffer );

    mesh_interface_ptr meshInterface( polymesh3_interface::create_instance( mesh ).release() );
    polymesh3_ptr fixed = fix_degenerate_faces( meshInterface );

    ASSERT_EQ( 3, fixed->face_count() );
    polymesh3_const_vertex_accessor<void> geomAcc = fixed->get_const_vertex_accessor( _T("verts") );

    polymesh3_const_vertex_accessor<void>::const_face_range face = geomAcc.get_face( 0 );
    ASSERT_EQ( 4, face.second - face.first );
    EXPECT_EQ( 0, face.first[0] );
    EXPECT_EQ( 1, face.first[1] );
    EXPECT_EQ( 2, face.first[2] );
    EXPECT_EQ( 3, face.first[3] );

    face = geomAcc.get_face( 1 );
    ASSERT_EQ( 3, face.second - face.first );
    EXPECT_EQ( 2, face.first[0] );
    EXPECT_EQ( 1, face.first[1] );
    EXPECT_EQ( 6, face.first[2] );

    face = geomAcc.get_face( 2 );
    ASSERT_EQ( 4, face.second - face.first );
    EXPECT_EQ( 1, face.first[0] );
    EXPECT_EQ( 4, face.first[1] );
    EXPECT_EQ( 5, face.first[2] );
    EXPECT_EQ( 6, face.first[3] );

    polymesh3_face_accessor<void> chanAcc = fixed->get_face_accessor( _T("Channel") );
    ASSERT_EQ( 3, chanAcc.face_count() );

    const float* chanFace = reinterpret_cast<float*>( chanAcc.get_face( 0 ) );
    EXPECT_EQ( 5, *chanFace );

    chanFace = reinterpret_cast<float*>( chanAcc.get_face( 1 ) );
    EXPECT_EQ( 6, *chanFace );

    chanFace = reinterpret_cast<float*>( chanAcc.get_face( 2 ) );
    EXPECT_EQ( 7, *chanFace );
}

TEST( MeshInterfaceUtils, ComputeVertexNormals ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 1, 0, 0 );
    mesh.add_vertex( 1, 1, 0 );

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 0, 1, 1 );
    mesh.add_vertex( 0, 0, 1 );

    mesh.add_face( 0, 1, 2 );
    mesh.add_face( 3, 4, 5 );

    frantic::geometry::mesh_interface::ptr_type i(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    std::vector<frantic::graphics::vector3f> vertexNormals;

    frantic::geometry::compute_vertex_normals( i.get(), vertexNormals );

    ASSERT_EQ( vertexNormals.size(), 6 );

    EXPECT_EQ( vertexNormals[0], frantic::graphics::vector3f( 0, 0, 1 ) );
    EXPECT_EQ( vertexNormals[1], frantic::graphics::vector3f( 0, 0, 1 ) );
    EXPECT_EQ( vertexNormals[2], frantic::graphics::vector3f( 0, 0, 1 ) );

    EXPECT_EQ( vertexNormals[3], frantic::graphics::vector3f( 1, 0, 0 ) );
    EXPECT_EQ( vertexNormals[4], frantic::graphics::vector3f( 1, 0, 0 ) );
    EXPECT_EQ( vertexNormals[5], frantic::graphics::vector3f( 1, 0, 0 ) );
}

TEST( MeshInterfaceUtils, CreateVertexNormalChannelMeshInterface ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 1, 0, 0 );
    mesh.add_vertex( 1, 1, 0 );

    mesh.add_face( 0, 1, 2 );

    // add a Color channel, which should be propagated to the output mesh
    mesh.add_vertex_channel<frantic::graphics::vector3f>( _T("Color") );
    {
        frantic::geometry::trimesh3_vertex_channel_accessor<frantic::graphics::vector3f> acc(
            mesh.get_vertex_channel_accessor<frantic::graphics::vector3f>( _T("Color") ) );
        acc[0].set( 1, 0, 0 );
        acc[1].set( 0, 1, 0 );
        acc[2].set( 0, 0, 1 );
    }

    // add a FaceSelection channel, which should be propagated to the output mesh
    mesh.add_face_channel<boost::int32_t>( _T("FaceSelection") );
    {
        frantic::geometry::trimesh3_face_channel_accessor<boost::int32_t> acc(
            mesh.get_face_channel_accessor<boost::int32_t>( _T("FaceSelection") ) );
        acc[0] = 1;
    }

    // we'll generate normals in this meshCopy later, for the sake of comparison
    frantic::geometry::trimesh3 meshCopy( mesh );

    frantic::geometry::mesh_interface::ptr_type mBeforeNormal(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    frantic::geometry::mesh_interface::ptr_type m(
        frantic::geometry::create_vertex_normal_channel_mesh_interface( mBeforeNormal.get() ) );

    ASSERT_TRUE( bool( m ) );
    ASSERT_TRUE( m->is_valid() );

    ASSERT_TRUE( m->get_vertex_channels().has_channel( _T("Color") ) );
    ASSERT_TRUE( m->get_vertex_channels().has_channel( _T("Normal") ) );
    ASSERT_TRUE( m->get_face_channels().has_channel( _T("FaceSelection") ) );

    meshCopy.build_vertex_normals();

    frantic::geometry::mesh_interface::ptr_type mCopy(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( meshCopy ) ).release() );

    ASSERT_TRUE( frantic::geometry::is_equal( m, mCopy ) );
}

TEST( MeshInterfaceUtils, CreateVertexNormalChannelMeshInterfaceReplaceVertexChannel ) {
    frantic::geometry::trimesh3 mesh;

    mesh.add_vertex( 0, 0, 0 );
    mesh.add_vertex( 1, 0, 0 );
    mesh.add_vertex( 1, 1, 0 );

    mesh.add_face( 0, 1, 2 );

    mesh.add_vertex_channel<int>( _T("Normal") );
    {
        frantic::geometry::trimesh3_vertex_channel_accessor<int> acc(
            mesh.get_vertex_channel_accessor<int>( _T("Normal") ) );
        for( std::size_t i = 0, ie = acc.size(); i < ie; ++i ) {
            acc[i] = -1;
        }
    }

    frantic::geometry::mesh_interface::ptr_type mBeforeNormal(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( mesh ) ).release() );
    frantic::geometry::mesh_interface::ptr_type m(
        frantic::geometry::create_vertex_normal_channel_mesh_interface( mBeforeNormal.get() ) );

    ASSERT_TRUE( bool( m ) );
    ASSERT_TRUE( m->is_valid() );

    const frantic::geometry::mesh_channel* normalChannel = m->get_vertex_channels().get_channel( _T("Normal" ) );

    ASSERT_TRUE( normalChannel != 0 );

    ASSERT_EQ( normalChannel->get_num_faces(), 1 );
    ASSERT_EQ( normalChannel->get_num_face_verts( 0 ), 3 );

    ASSERT_TRUE( frantic::geometry::mesh_channel::is_stored_at_vertex( normalChannel->get_channel_type() ) );

    ASSERT_EQ( normalChannel->get_data_type(), frantic::channels::data_type_float32 );
    ASSERT_EQ( normalChannel->get_data_arity(), 3 );

    frantic::geometry::mesh_channel_cvt<frantic::graphics::vector3f> acc( normalChannel );

    EXPECT_EQ( acc.get_value( acc.get_fv_index( 0, 0 ) ), frantic::graphics::vector3f( 0, 0, 1 ) );
    EXPECT_EQ( acc.get_value( acc.get_fv_index( 0, 1 ) ), frantic::graphics::vector3f( 0, 0, 1 ) );
    EXPECT_EQ( acc.get_value( acc.get_fv_index( 0, 2 ) ), frantic::graphics::vector3f( 0, 0, 1 ) );
}

TEST( MeshInterfaceUtils, CreateParticleArrayMeshInterface ) {
    using frantic::graphics::vector3f;

    std::unique_ptr<frantic::geometry::mesh_interface> mesh;

    { // scope for particles
        frantic::channels::channel_map channelMap;
        channelMap.define_channel<vector3f>( _T("Position") );
        channelMap.define_channel<vector3f>( _T("Color") );
        channelMap.end_channel_definition();

        frantic::particles::particle_array particles( channelMap );

        frantic::channels::channel_accessor<vector3f> positionAcc(
            particles.get_channel_map().get_accessor<vector3f>( _T("Position") ) );
        frantic::channels::channel_accessor<vector3f> colorAcc(
            particles.get_channel_map().get_accessor<vector3f>( _T("Color") ) );

        std::vector<char> buffer( particles.get_channel_map().structure_size() );

        positionAcc( buffer ).set( 0, 1, 2 );
        colorAcc( buffer ).set( 3, 4, 5 );

        particles.push_back( &buffer[0] );

        mesh = frantic::geometry::create_particle_array_mesh_interface( boost::move( particles ) );
    }

    ASSERT_TRUE( mesh.get() != 0 );
    ASSERT_TRUE( mesh->is_valid() );

    ASSERT_EQ( mesh->get_num_verts(), 1 );
    EXPECT_EQ( mesh->get_num_faces(), 0 );

    EXPECT_EQ( mesh->get_vert( 0 ), vector3f( 0, 1, 2 ) );

    EXPECT_FALSE( mesh->has_vertex_channel( _T("Position") ) );
    EXPECT_TRUE( mesh->has_vertex_channel( _T("Color") ) );

    const frantic::geometry::mesh_interface::mesh_channel_map& channels = mesh->get_vertex_channels();

    const frantic::geometry::mesh_channel* colorChannel = channels.get_channel( _T("Color") );

    ASSERT_TRUE( colorChannel != 0 );
    ASSERT_EQ( colorChannel->get_data_type(), frantic::channels::data_type_float32 );
    ASSERT_EQ( colorChannel->get_data_arity(), 3 );
    ASSERT_EQ( colorChannel->get_num_elements(), 1 );
    EXPECT_EQ( colorChannel->get_name(), _T("Color") );

    vector3f color;
    colorChannel->get_value( 0, &color[0] );
    EXPECT_EQ( color, vector3f( 3, 4, 5 ) );
}

TEST( MeshInterfaceUtils, FindFaceCorner ) {
    using namespace frantic::geometry;

    trimesh3 mesh;

    make_regular_tetrahedron( mesh );

    mesh_interface_ptr iMesh( trimesh3_interface::create_instance( boost::move( mesh ) ).release() );

    for( size_t faceId = 0; faceId < iMesh->get_num_faces(); ++faceId ) {
        for( size_t faceVertex = 0; faceVertex < 3; ++faceVertex ) {
            EXPECT_EQ( faceVertex,
                       find_face_corner( iMesh, faceId, iMesh->get_face_vert_index( faceId, faceVertex ) ) );
        }

        EXPECT_ANY_THROW( find_face_corner( iMesh, faceId, faceId ) );
    }
}

TEST( MeshInterfaceUtils, IsTriangleMesh ) {
    using namespace frantic::geometry;

    trimesh3 trimesh;
    make_quad_trimesh( trimesh );
    mesh_interface::ptr_type tri(
        frantic::geometry::trimesh3_interface::create_instance( boost::move( trimesh ) ).release() );

    polymesh3_ptr polymesh = make_quad_polymesh();
    mesh_interface::ptr_type poly( frantic::geometry::polymesh3_interface::create_instance( polymesh ) );

    EXPECT_TRUE( is_triangle_mesh( tri.get() ) );
    EXPECT_FALSE( is_triangle_mesh( poly.get() ) );
}

TEST( MeshInterfaceUtils, AssertValidIndices ) {
    using namespace frantic::geometry;

    trimesh3 original;
    original.add_vertex( 0, 0, 0 );
    original.add_vertex( 1, 0, 0 );
    original.add_vertex( 1, 1, 0 );
    original.add_face( 0, 1, 2 );

    original.add_vertex_channel<float>( _T("Channel"), 1, true );

    EXPECT_NO_THROW( assert_valid_indices( trimesh3_interface::create_instance( &original ).get() ) );

    trimesh3 badFaceIndex( original );
    badFaceIndex.get_face( 0 )[0] = 3;

    EXPECT_ANY_THROW( assert_valid_indices( trimesh3_interface::create_instance( &badFaceIndex ).get() ) );

    trimesh3 badChannelIndex( original );
    trimesh3_vertex_channel_accessor<float> acc = badChannelIndex.get_vertex_channel_accessor<float>( _T("Channel") );
    acc.face( 0 )[2] = 1;

    EXPECT_ANY_THROW( assert_valid_indices( trimesh3_interface::create_instance( &badChannelIndex ).get() ) );
}