#include #include // Used for per-vertex colors UF_VERTEX_DESCRIPTOR(pod::Vertex_3F2F3F4F, UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F4F, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F4F, R32G32_SFLOAT, uv) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F4F, R32G32B32_SFLOAT, normal) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F4F, R32_UINT, color) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F2F3F4F, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), uf::vector::normalize( uf::vector::lerp( p1.normal, p2.normal, t ) ), uf::vector::lerp( p1.color, p2.color, t ), }; }) // Used for terrain UF_VERTEX_DESCRIPTOR(pod::Vertex_3F2F3F32B, UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F32B, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F32B, R32G32_SFLOAT, uv) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F32B, R32G32B32_SFLOAT, normal) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F32B, R32_UINT, color) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F2F3F32B, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), uf::vector::normalize( uf::vector::lerp( p1.normal, p2.normal, t ) ), t < 0.5 ? p1.color : p2.color, //uf::vector::lerp( p1.color, p2.color, t ), }; }) // Used for normal meshses UF_VERTEX_DESCRIPTOR(pod::Vertex_3F2F3F, UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F, R32G32_SFLOAT, uv) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F, R32G32B32_SFLOAT, normal) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F3F3F, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), uf::vector::normalize( uf::vector::lerp( p1.normal, p2.normal, t ) ), }; }) // (Typically) used for displaying textures UF_VERTEX_DESCRIPTOR(pod::Vertex_3F2F, UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F, R32G32_SFLOAT, uv) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F2F3F1UI, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), uf::vector::normalize( uf::vector::lerp( p1.normal, p2.normal, t ) ), uf::vector::lerp( p1.id, p2.id, t ), }; }) UF_VERTEX_DESCRIPTOR(pod::Vertex_2F2F, UF_VERTEX_DESCRIPTION(pod::Vertex_2F2F, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_2F2F, R32G32_SFLOAT, uv) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F2F3F, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), uf::vector::normalize( uf::vector::lerp( p1.normal, p2.normal, t ) ), }; }) // used for texture arrays UF_VERTEX_DESCRIPTOR(pod::Vertex_3F3F3F, UF_VERTEX_DESCRIPTION(pod::Vertex_3F3F3F, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F3F3F, R32G32B32_SFLOAT, uv) UF_VERTEX_DESCRIPTION(pod::Vertex_3F3F3F, R32G32B32_SFLOAT, normal) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F2F, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), }; }) UF_VERTEX_DESCRIPTOR(pod::Vertex_3F2F3F1UI, UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F1UI, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F1UI, R32G32_SFLOAT, uv) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F1UI, R32G32B32_SFLOAT, normal) UF_VERTEX_DESCRIPTION(pod::Vertex_3F2F3F1UI, R32_UINT, id) ) UF_VERTEX_INTERPOLATE(pod::Vertex_2F2F, { return { uf::vector::lerp( p1.position, p2.position, t ), uf::vector::lerp( p1.uv, p2.uv, t ), }; }) // Basic UF_VERTEX_DESCRIPTOR(pod::Vertex_3F, UF_VERTEX_DESCRIPTION(pod::Vertex_3F, R32G32B32_SFLOAT, position) ) UF_VERTEX_INTERPOLATE(pod::Vertex_3F, { return { uf::vector::lerp( p1.position, p2.position, t ), }; }) void uf::Mesh::initialize() {} void uf::Mesh::destroy() { _destroy(vertex); _destroy(index); _destroy(instance); _destroy(indirect); buffers.clear(); } uf::Mesh uf::Mesh::copy() const { uf::Mesh res; res.bind( *this ); res.insert(*this); res.updateDescriptor(); return res; } uf::Mesh& uf::Mesh::copy( const uf::Mesh& src ) { if ( src.buffers.empty() ) return *this; bind( src ); insert( src ); updateDescriptor(); return *this; } uf::Mesh uf::Mesh::alias() const { uf::Mesh alias = *this; for ( auto& buf : alias.buffers ) buf.clear(); alias.updateDescriptor(); return alias; } void uf::Mesh::updateDescriptor() { _updateDescriptor(vertex); _updateDescriptor(index); _updateDescriptor(instance); _updateDescriptor(indirect); _updateViews(); } void uf::Mesh::bind( const uf::Mesh& mesh ) { vertex.attributes = mesh.vertex.attributes; index.attributes = mesh.index.attributes; instance.attributes = mesh.instance.attributes; indirect.attributes = mesh.indirect.attributes; _bind(); } void uf::Mesh::insert( const uf::Mesh& mesh ) { if ( vertex.attributes.empty() && index.attributes.empty() && instance.attributes.empty() && indirect.attributes.empty() ) bind( mesh ); insertVertices(mesh); insertIndices(mesh); // insertInstances(mesh); insertIndirects(mesh); updateDescriptor(); } void uf::Mesh::generateIndices() { // deduce type size_t size = sizeof(uint32_t); uf::renderer::enums::Type::type_t type{}; /*if ( vertex.count <= std::numeric_limits::max() ) { size = sizeof(uint8_t); type = uf::renderer::typeToEnum(); } else*/ if ( vertex.count <= std::numeric_limits::max() ) { size = sizeof(uint16_t); type = uf::renderer::typeToEnum(); } else if ( vertex.count <= std::numeric_limits::max() ) { size = sizeof(uint32_t); type = uf::renderer::typeToEnum(); } if ( !index.attributes.empty() ) { _destroy( index ); } _bindI( index, size, type ); _bind(); switch ( size ) { case 1: { uf::stl::vector indices( vertex.count ); std::iota( indices.begin(), indices.end(), 0 ); insertIndices( indices ); } break; case 2: { uf::stl::vector indices( vertex.count ); std::iota( indices.begin(), indices.end(), 0 ); insertIndices( indices ); } break; case 4: { uf::stl::vector indices( vertex.count ); std::iota( indices.begin(), indices.end(), 0 ); insertIndices( indices ); } break; } } uf::Mesh uf::Mesh::expand( ) { uf::Mesh res = copy(); res.resizeVertices( index.count ); res.vertex.count = index.count; auto& srcIndex = index.attributes.front(); auto& dstIndex = res.index.attributes.front(); #define GET_INDEX(T) {\ index = *(const T*) (static_cast(srcIndex.pointer) + idx * srcIndex.stride);\ *((T*) (static_cast(dstIndex.pointer) + idx * dstIndex.stride)) = idx;\ } for ( size_t idx = 0; idx < index.count; ++idx ) { size_t index = 0; switch ( srcIndex.descriptor.size ) { case 1: GET_INDEX(uint8_t); break; case 2: GET_INDEX(uint16_t); break; case 4: GET_INDEX(uint32_t); break; } for ( size_t _ = 0; _ < vertex.attributes.size(); ++_ ) { auto& srcInput = vertex.attributes[_]; auto& dstInput = res.vertex.attributes[_]; uint8_t* srcAddr = static_cast(srcInput.pointer) + index * srcInput.stride; uint8_t* dstAddr = static_cast(dstInput.pointer) + idx * dstInput.stride; memcpy( dstAddr, srcAddr, srcInput.descriptor.size ); } } #undef GET_INDEX if ( res.indirect.count ) { pod::DrawCommand* drawCommands = (pod::DrawCommand*) res.getBuffer(res.indirect).data(); for ( size_t i = 0, vertexID = 0; i < res.indirect.count; ++i ) { auto& drawCommand = drawCommands[i]; drawCommand.vertexID = vertexID; drawCommand.vertices = drawCommand.indices; vertexID += drawCommand.indices; } } res.updateDescriptor(); return res; } void uf::Mesh::clearAttribute( uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute ) { for ( size_t i = 0; i < input.attributes.size(); ++i ) if ( input.attributes[i].descriptor == attribute.descriptor ) return clearAttribute( input, i ); } void uf::Mesh::clearAttribute( uf::Mesh::Input& input, size_t i ) { auto attribute = input.attributes[i]; buffers[attribute.buffer].clear(); } void uf::Mesh::clear() { for ( size_t i = 0; i < vertex.attributes.size(); ++i ) clearAttribute( vertex, i ); for ( size_t i = 0; i < index.attributes.size(); ++i ) clearAttribute( index, i ); for ( size_t i = 0; i < instance.attributes.size(); ++i ) clearAttribute( instance, i ); for ( size_t i = 0; i < indirect.attributes.size(); ++i ) clearAttribute( indirect, i ); vertex.count = 0; index.count = 0; instance.count = 0; indirect.count = 0; } void uf::Mesh::generateIndirect() { if ( index.count == 0 ) generateIndices(); uf::stl::vector commands; for ( auto& attribute : index.attributes ) { auto& buffer = getBuffer(index, attribute); commands.emplace_back(pod::DrawCommand{ .indices = buffer.size() / attribute.descriptor.size, .instances = instance.count == 0 && instance.attributes.empty() ? 1 : instance.count, .indexID = 0, .vertexID = 0, .instanceID = 0, // .materialID = 0, // .objectID = 0, .vertices = vertex.count, }); } _destroy( indirect ); bindIndirect(); _bind(); insertIndirects( commands ); } uf::Mesh::buffer_t& uf::Mesh::getBuffer( const uf::Mesh::Input& input, size_t i ) { return getBuffer( input, input.attributes[i] ); } uf::Mesh::buffer_t& uf::Mesh::getBuffer( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute ) { return buffers[attribute.buffer]; } const uf::Mesh::buffer_t& uf::Mesh::getBuffer( const uf::Mesh::Input& input, size_t i ) const { return getBuffer( input, input.attributes[i] ); } const uf::Mesh::buffer_t& uf::Mesh::getBuffer( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute ) const { return buffers[attribute.buffer]; } uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector& wanted, size_t lod ) const { uf::Mesh::View view; view.vertex = vertex; view.index = index; if ( wanted.size() ) { for ( auto& attr : vertex.attributes ) { if ( std::find(wanted.begin(), wanted.end(), attr.descriptor.name ) == wanted.end() ) continue; view.attributes[uf::algo::fnv1a(attr.descriptor.name)] = { attr }; } } else { for ( auto& attr : vertex.attributes ) { view.attributes[uf::algo::fnv1a(attr.descriptor.name)] = { attr }; } } if ( !index.attributes.empty() ) { view.attributes["index"_hash] = { index.attributes[lod] }; } return view; } uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector& wanted, size_t lod ) const { uf::Mesh::View view; view.vertex = remapVertexInput(i, lod); view.index = remapIndexInput(i, lod); view.indirectIndex = i; if ( wanted.size() ) { for (auto& attr : vertex.attributes) { if ( std::find(wanted.begin(), wanted.end(), attr.descriptor.name ) == wanted.end() ) continue; view.attributes[uf::algo::fnv1a(attr.descriptor.name)] = { attr }; } } else { for ( auto& attr : vertex.attributes ) view.attributes[uf::algo::fnv1a(attr.descriptor.name)] = { attr }; } if ( !index.attributes.empty() ) { view.attributes["index"_hash] = { index.attributes[lod] }; } return view; } uf::stl::vector uf::Mesh::makeViews( const uf::stl::vector& wanted, size_t lod ) const { uf::stl::vector views; if ( indirect.count > 0 ) { for ( auto i = 0; i < indirect.count; i++ ) { auto view = makeView( i, wanted, lod ); if ( view.index.count == 0 && view.vertex.count == 0 ) continue; views.emplace_back( view ); } } else { views.emplace_back( makeView(wanted, lod) ); } return views; } uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i, size_t lod ) const { uf::Mesh::Input res = input; UF_ASSERT( &input == &vertex || &input == &index ); UF_ASSERT( i < indirect.count ); const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = &input == &vertex ? drawCommand.vertexID : drawCommand.indexID; res.count = &input == &vertex ? drawCommand.vertices : drawCommand.indices; return res; } uf::Mesh::Input uf::Mesh::remapVertexInput( size_t i, size_t lod ) const { uf::Mesh::Input res = vertex; UF_ASSERT( i < indirect.count ); const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = drawCommand.vertexID; res.count = drawCommand.vertices; return res; } uf::Mesh::Input uf::Mesh::remapIndexInput( size_t i, size_t lod ) const { uf::Mesh::Input res = index; UF_ASSERT( i < indirect.count ); const auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect, lod).data())[i]; res.first = drawCommand.indexID; res.count = drawCommand.indices; return res; } // void uf::Mesh::_destroy( uf::Mesh::Input& input ) { for ( auto& attribute : input.attributes ) { attribute.length = 0; attribute.pointer = NULL; attribute.buffer = 0; } input.attributes.clear(); } void uf::Mesh::_bind() { int32_t buffer = 0; auto parse_input = [&](uf::Mesh::Input& input) { for ( auto& attribute : input.attributes ) { attribute.buffer = buffer++; attribute.pointer = NULL; } }; parse_input(vertex); parse_input(index); parse_input(instance); parse_input(indirect); buffers.resize( buffer ); updateDescriptor(); } void uf::Mesh::_updateDescriptor( uf::Mesh::Input& input ) { input.size = 0; for ( auto& attribute : input.attributes ) { if ( attribute.buffer >= 0 && attribute.buffer < buffers.size() && !buffers[attribute.buffer].empty() ) { auto& buffer = buffers[attribute.buffer]; attribute.length = buffer.size(); attribute.pointer = buffer.data() + attribute.offset; } if ( &input == &index || &input == &indirect ) input.size = attribute.descriptor.size; else input.size += attribute.descriptor.size; attribute.stride = attribute.descriptor.size; } } void uf::Mesh::_updateViews() { buffer_views = makeViews(); } uf::Mesh::Attribute uf::Mesh::_remapAttribute( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute, size_t i ) const { UF_ASSERT( i < indirect.count ); UF_ASSERT( &input == &vertex || &input == &index ); UF_MSG_WARNING( "deprecated, please use remapInput" ); uf::Mesh::Attribute res = attribute; auto& drawCommand = ((const pod::DrawCommand*) getBuffer(indirect).data())[i]; if ( &input == &vertex ) { res.pointer = static_cast(res.pointer) + drawCommand.vertexID * res.stride; res.length = drawCommand.vertices * res.stride; } else if ( &input == &index ) { res.pointer = static_cast(res.pointer) + drawCommand.indexID * res.stride; res.length = drawCommand.indices * res.stride; } return res; } void uf::Mesh::_insertVs( uf::Mesh::Input& dstInput, const uf::Mesh& mesh, const uf::Mesh::Input& srcInput ) { _reserveVs( dstInput, dstInput.count += srcInput.count ); if ( _hasV( dstInput, srcInput ) ) { for ( auto i = 0; i < dstInput.attributes.size(); ++i ) { auto& src = mesh.buffers[srcInput.attributes[i].buffer]; auto& dst = buffers[dstInput.attributes[i].buffer]; dst.insert( dst.end(), src.begin(), src.end() ); } } else { for ( auto& dstAttribute : dstInput.attributes ) { for ( auto& srcAttribute : srcInput.attributes ) { if ( srcAttribute.descriptor != dstAttribute.descriptor ) continue; auto& src = mesh.buffers[srcAttribute.buffer]; auto& dst = buffers[dstAttribute.buffer]; dst.insert( dst.end(), src.begin(), src.end() ); break; } } } _updateDescriptor( dstInput ); } void uf::Mesh::_insertIs( uf::Mesh::Input& dstInput, const uf::Mesh& mesh, const uf::Mesh::Input& srcInput ) { _reserveIs( dstInput, dstInput.count += srcInput.count ); for ( auto i = 0; i < dstInput.attributes.size(); ++i ) { auto& src = mesh.getBuffer( srcInput, i ); auto& dst = getBuffer( dstInput, i ); dst.insert( dst.end(), src.begin(), src.end() ); } _updateDescriptor( dstInput ); } // Vertices bool uf::Mesh::_hasV( const uf::Mesh::Input& input, const uf::stl::vector& descriptors ) const { if ( input.attributes.size() != descriptors.size() ) return false; for ( auto i = 0; i < input.attributes.size(); ++i ) if ( input.attributes[i].descriptor != descriptors[i] ) return false; return true; } bool uf::Mesh::_hasV( const uf::Mesh::Input& input, const uf::Mesh::Input& srcInput ) const { if ( input.attributes.size() != srcInput.attributes.size() || input.size != srcInput.size ) return false; for ( auto i = 0; i < input.attributes.size(); ++i ) if ( input.attributes[i].descriptor != srcInput.attributes[i].descriptor ) return false; return true; } void uf::Mesh::_bindV( uf::Mesh::Input& input, const uf::stl::vector& descriptors ) { input.attributes.resize( descriptors.size() ); for ( auto i = 0; i < descriptors.size(); ++i ) { auto& attribute = input.attributes[i]; attribute.descriptor = descriptors[i]; input.size += attribute.descriptor.size; } } void uf::Mesh::_reserveVs( uf::Mesh::Input& input, size_t count ) { for ( auto& attribute : input.attributes ) { buffers[attribute.buffer].reserve( count * attribute.descriptor.size ); attribute.length = buffers[attribute.buffer].size(); attribute.pointer = (uint8_t*) (buffers[attribute.buffer].data()); } } void uf::Mesh::_resizeVs( uf::Mesh::Input& input, size_t count ) { for ( auto& attribute : input.attributes ) { buffers[attribute.buffer].resize( count * attribute.descriptor.size ); attribute.length = buffers[attribute.buffer].size(); attribute.pointer = (uint8_t*) (buffers[attribute.buffer].data()); } } void uf::Mesh::_insertV( uf::Mesh::Input& input, const void* data ) { _reserveVs( input, ++input.count ); const uint8_t* pointer = (const uint8_t*) data; for ( auto& attribute : input.attributes ) { buffers[attribute.buffer].insert( buffers[attribute.buffer].end(), pointer + attribute.descriptor.offset, pointer + attribute.descriptor.offset + attribute.descriptor.size ); } } void uf::Mesh::_insertVs( uf::Mesh::Input& input, const void* data, size_t size ) { size_t count = input.count; input.count += size; _resizeVs( input, input.count ); const uint8_t* pointer = static_cast(data); for ( auto& attribute : input.attributes ) { uint8_t* dstBase = buffers[attribute.buffer].data() + (count * attribute.descriptor.size); size_t srcOffset = attribute.descriptor.offset; size_t attrSize = attribute.descriptor.size; for ( size_t i = 0; i < size; ++i ) { const uint8_t* srcAddr = pointer + (i * input.size) + srcOffset; uint8_t* dstAddr = dstBase + (i * attrSize); memcpy( dstAddr, srcAddr, attrSize ); } } } // Indices void uf::Mesh::_bindI( uf::Mesh::Input& input, size_t size, ext::RENDERER::enums::Type::type_t type, size_t count ) { input.attributes.resize( count ); input.size = size; for ( auto i = 0; i < count; ++i ) { auto& attribute = input.attributes[i]; attribute.descriptor.offset = 0; attribute.descriptor.size = size; attribute.descriptor.components = 1; attribute.descriptor.type = type; } } void uf::Mesh::_reserveIs( uf::Mesh::Input& input, size_t count, size_t i ) { auto& attribute = input.attributes[i]; buffers[attribute.buffer].reserve( count * attribute.descriptor.size ); attribute.length = buffers[attribute.buffer].size(); attribute.pointer = (uint8_t*) (buffers[attribute.buffer].data()); } void uf::Mesh::_resizeIs( uf::Mesh::Input& input, size_t count, size_t i ) { auto& attribute = input.attributes[i]; buffers[attribute.buffer].resize( count * attribute.descriptor.size ); attribute.length = buffers[attribute.buffer].size(); attribute.pointer = (uint8_t*) (buffers[attribute.buffer].data()); } void uf::Mesh::_insertI( uf::Mesh::Input& input, const void* data, size_t i ) { auto& attribute = input.attributes[i]; _reserveIs( input, ++input.count ); const uint8_t* pointer = (const uint8_t*) data; buffers[attribute.buffer].insert( buffers[attribute.buffer].end(), pointer, pointer + attribute.descriptor.size ); } void uf::Mesh::_insertIs( uf::Mesh::Input& input, const void* data, size_t size, size_t i ) { auto& attribute = input.attributes[i]; _reserveIs( input, input.count += size ); const uint8_t* pointer = (const uint8_t*) data; for ( const uint8_t* p = pointer; p < pointer + size * attribute.descriptor.size; p += attribute.descriptor.size ) buffers[attribute.buffer].insert( buffers[attribute.buffer].end(), p + attribute.descriptor.offset, p + attribute.descriptor.offset + attribute.descriptor.size ); } //// void uf::mesh::setIndex( void* pointer, size_t stride, size_t index, size_t value ) { switch ( stride ) { case 1: ((uint8_t*) pointer)[index] = static_cast(value); break; case 2: ((uint16_t*) pointer)[index] = static_cast(value); break; case 4: ((uint32_t*) pointer)[index] = static_cast(value); break; default: { UF_EXCEPTION("invalid stride type: {}", stride); } break; } } size_t uf::mesh::fetchIndex( const void* pointer, size_t stride, size_t index ) { #define CAST_INDEX(T) case sizeof(T): return ((T*) pointer)[index]; switch ( stride ) { CAST_INDEX(uint8_t); CAST_INDEX(uint16_t); CAST_INDEX(uint32_t); default: { UF_EXCEPTION("invalid stride type: {}", stride); } break; } } pod::Vector3f uf::mesh::fetchVertex( const uf::Mesh::View& view, const uf::Mesh::AttributeView& positions, size_t index ) { return uf::mesh::fetchVertexAttribute( view, positions, index ); } pod::Triangle uf::mesh::fetchTriangle( const uf::Mesh::View& view, const uf::Mesh::AttributeView& indices, const uf::Mesh::AttributeView& positions, size_t triID ) { auto index = triID * 3; pod::Triangle tri; FOR_EACH(3, { tri.points[i] = uf::mesh::fetchVertex( view, positions, uf::mesh::fetchIndex( view, indices, index + i ) ); }); return tri; } pod::TriangleWithNormal uf::mesh::fetchTriangle( const uf::Mesh& mesh, size_t triID ) { const auto& views = mesh.buffer_views; UF_ASSERT(!views.empty()); // find which view contains this triangle index. size_t triBase = 0; const uf::Mesh::View* view = nullptr; for ( auto& v : views ) { auto trisInView = v.index.count / 3; if (triID < triBase + trisInView) { view = &v; triID -= triBase; // local triangle index inside this view break; } triBase += trisInView; } UF_ASSERT( view ); pod::TriangleWithNormal tri = { uf::mesh::fetchTriangle( *view, triID ) }; tri.normal = uf::vector::normalize(uf::vector::cross(tri.points[1] - tri.points[0], tri.points[2] - tri.points[0])); return tri; }