/*
This file is part of Magnum .
Copyright © 2010 , 2011 , 2012 , 2013 , 2014 , 2015 , 2016 , 2017 , 2018 , 2019
Vladimír Vondruš < mosra @ centrum . cz >
Permission is hereby granted , free of charge , to any person obtaining a
copy of this software and associated documentation files ( the " Software " ) ,
to deal in the Software without restriction , including without limitation
the rights to use , copy , modify , merge , publish , distribute , sublicense ,
and / or sell copies of the Software , and to permit persons to whom the
Software is furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE .
*/
# include "RemoveDuplicates.h"
# include <cstring>
# include <Corrade/Containers/StridedArrayView.h>
# include <Corrade/Utility/Algorithms.h>
# include "Magnum/MeshTools/Concatenate.h"
# include "Magnum/MeshTools/Interleave.h"
# include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace MeshTools {
struct ArrayEqual {
bool operator ( ) ( Containers : : ArrayView < const char > a , Containers : : ArrayView < const char > b ) const {
CORRADE_INTERNAL_ASSERT ( a . size ( ) = = b . size ( ) ) ;
return std : : memcmp ( a , b , a . size ( ) ) = = 0 ;
}
} ;
struct ArrayHash {
std : : size_t operator ( ) ( Containers : : ArrayView < const char > a ) const {
return * reinterpret_cast < const std : : size_t * > ( Utility : : MurmurHash2 { } ( a , a . size ( ) ) . byteArray ( ) ) ;
}
} ;
std : : size_t removeDuplicatesInto ( const Containers : : StridedArrayView2D < const char > & data , const Containers : : StridedArrayView1D < UnsignedInt > & indices ) {
/* Assuming the second dimension is contiguous so we can calculate the
hashes easily */
CORRADE_ASSERT ( data . empty ( ) [ 0 ] | | data . isContiguous < 1 > ( ) ,
" MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous " , { } ) ;
const std : : size_t dataSize = data . size ( ) [ 0 ] ;
CORRADE_ASSERT ( indices . size ( ) = = dataSize ,
" MeshTools::removeDuplicatesInto(): output index array has " < < indices . size ( ) < < " elements but expected " < < dataSize , { } ) ;
/* Table containing index of first occurence for each unique entry.
Reserving more buckets than necessary ( i . e . as if each entry was
unique ) . */
std : : unordered_map < Containers : : ArrayView < const char > , UnsignedInt , ArrayHash , ArrayEqual > table { dataSize } ;
/* Go through all entries */
for ( std : : size_t i = 0 ; i ! = dataSize ; + + i ) {
/* Try to insert new entry into the table. The inserted index points
into the original unchanged data array . */
const Containers : : ArrayView < const char > entry = data [ i ] . asContiguous ( ) ;
const auto result = table . emplace ( entry , i ) ;
/* Put the (either new or already existing) index into the output
index array */
indices [ i ] = result . first - > second ;
}
CORRADE_INTERNAL_ASSERT ( dataSize > = table . size ( ) ) ;
return table . size ( ) ;
}
std : : pair < Containers : : Array < UnsignedInt > , std : : size_t > removeDuplicates ( const Containers : : StridedArrayView2D < const char > & data ) {
Containers : : Array < UnsignedInt > indices { Containers : : NoInit , data . size ( ) [ 0 ] } ;
const std : : size_t size = removeDuplicatesInto ( data , indices ) ;
return { std : : move ( indices ) , size } ;
}
std : : size_t removeDuplicatesInPlaceInto ( const Containers : : StridedArrayView2D < char > & data , const Containers : : StridedArrayView1D < UnsignedInt > & indices ) {
/* Assuming the second dimension is contiguous so we can calculate the
hashes easily */
CORRADE_ASSERT ( data . empty ( ) [ 0 ] | | data . isContiguous < 1 > ( ) ,
" MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous " , { } ) ;
const std : : size_t dataSize = data . size ( ) [ 0 ] ;
CORRADE_ASSERT ( indices . size ( ) = = dataSize ,
" MeshTools::removeDuplicatesInPlaceInto(): output index array has " < < indices . size ( ) < < " elements but expected " < < dataSize , { } ) ;
/* Table containing index of first occurence for each unique entry.
Reserving more buckets than necessary ( i . e . as if each entry was
unique ) . */
std : : unordered_map < Containers : : ArrayView < const char > , UnsignedInt , ArrayHash , ArrayEqual > table { dataSize } ;
/* Go through all entries */
for ( std : : size_t i = 0 ; i ! = dataSize ; + + i ) {
/* Try to insert new entry into the table. The inserted index points
into the new data array that has all duplicates removed . */
const Containers : : ArrayView < const char > entry = data [ i ] . asContiguous ( ) ;
const auto result = table . emplace ( entry , table . size ( ) ) ;
/* Put the (either new or already existing) index into the output index
array */
indices [ i ] = result . first - > second ;
/* If this is a new combination, copy the data to new (earlier)
position in the array . Data in [ table . size ( ) - 1 , i ) are already
present in the [ 0 , table . size ( ) - 1 ) range from previous iterations so
we aren ' t overwriting anything . */
if ( result . second & & i ! = table . size ( ) - 1 )
Utility : : copy ( entry , data [ table . size ( ) - 1 ] . asContiguous ( ) ) ;
}
CORRADE_INTERNAL_ASSERT ( dataSize > = table . size ( ) ) ;
return table . size ( ) ;
}
std : : pair < Containers : : Array < UnsignedInt > , std : : size_t > removeDuplicatesInPlace ( const Containers : : StridedArrayView2D < char > & data ) {
Containers : : Array < UnsignedInt > indices { Containers : : NoInit , data . size ( ) [ 0 ] } ;
const std : : size_t size = removeDuplicatesInPlaceInto ( data , indices ) ;
return { std : : move ( indices ) , size } ;
}
namespace {
template < class IndexType > std : : size_t removeDuplicatesIndexedInPlaceImplementation ( const Containers : : StridedArrayView1D < IndexType > & indices , const Containers : : StridedArrayView2D < char > & data ) {
/* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is
int ( - 1 ) instead of the type I want */
CORRADE_ASSERT ( data . size ( ) [ 0 ] < = IndexType ( - 1 ) ,
" MeshTools::removeDuplicatesIndexedInPlace(): a " < < sizeof ( IndexType ) < < Debug : : nospace < < " -byte index type is too small for " < < data . size ( ) [ 0 ] < < " vertices " , { } ) ;
/* There's no way to avoid the additional allocation, unfortunately ---
iterating over the indices instead of data would not preserve the
original order , which is an useful property . The float version has this
inverted ( having the * Indexed ( ) variant as the main implementation )
because the remapping there has to be done once for every dimension . */
std : : pair < Containers : : Array < UnsignedInt > , std : : size_t > result = removeDuplicatesInPlace ( data ) ;
for ( auto & i : indices ) i = result . first [ i ] ;
return result . second ;
}
}
std : : size_t removeDuplicatesIndexedInPlace ( const Containers : : StridedArrayView1D < UnsignedInt > & indices , const Containers : : StridedArrayView2D < char > & data ) {
return removeDuplicatesIndexedInPlaceImplementation ( indices , data ) ;
}
std : : size_t removeDuplicatesIndexedInPlace ( const Containers : : StridedArrayView1D < UnsignedShort > & indices , const Containers : : StridedArrayView2D < char > & data ) {
return removeDuplicatesIndexedInPlaceImplementation ( indices , data ) ;
}
std : : size_t removeDuplicatesIndexedInPlace ( const Containers : : StridedArrayView1D < UnsignedByte > & indices , const Containers : : StridedArrayView2D < char > & data ) {
return removeDuplicatesIndexedInPlaceImplementation ( indices , data ) ;
}
std : : size_t removeDuplicatesIndexedInPlace ( const Containers : : StridedArrayView2D < char > & indices , const Containers : : StridedArrayView2D < char > & data ) {
CORRADE_ASSERT ( indices . isContiguous < 1 > ( ) , " MeshTools::removeDuplicatesIndexedInPlace(): second index view dimension is not contiguous " , { } ) ;
if ( indices . size ( ) [ 1 ] = = 4 )
return removeDuplicatesIndexedInPlace ( Containers : : arrayCast < 1 , UnsignedInt > ( indices ) , data ) ;
else if ( indices . size ( ) [ 1 ] = = 2 )
return removeDuplicatesIndexedInPlace ( Containers : : arrayCast < 1 , UnsignedShort > ( indices ) , data ) ;
else {
CORRADE_ASSERT ( indices . size ( ) [ 1 ] = = 1 , " MeshTools::removeDuplicatesIndexedInPlace(): expected index type size 1, 2 or 4 but got " < < indices . size ( ) [ 1 ] , { } ) ;
return removeDuplicatesIndexedInPlace ( Containers : : arrayCast < 1 , UnsignedByte > ( indices ) , data ) ;
}
}
Trade : : MeshData removeDuplicates ( const Trade : : MeshData & data ) {
return removeDuplicates ( Trade : : MeshData { data . primitive ( ) ,
{ } , data . indexData ( ) , Trade : : MeshIndexData { data . indices ( ) } ,
{ } , data . vertexData ( ) , Trade : : meshAttributeDataNonOwningArray ( data . attributeData ( ) ) ,
data . vertexCount ( ) } ) ;
}
Trade : : MeshData removeDuplicates ( Trade : : MeshData & & data ) {
CORRADE_ASSERT ( data . attributeCount ( ) ,
" MeshTools::removeDuplicates(): can't remove duplicates in an attributeless mesh " ,
( Trade : : MeshData { MeshPrimitive : : Points , 0 } ) ) ;
/* Turn the passed data into an interleaved owned mutable instance we can
operate on - - concatenate ( ) alone only makes the data owned ,
interleave ( ) alone only makes the data interleaved ( but those can stay
non - owned ) . There ' s a chance the original data are already like this , in
which case this will be just a passthrough . */
/** @todo concatenate() causes the resulting index type to be UnsignedInt
always , replace with owned ( ) or some such when that ' s done */
Trade : : MeshData ownedInterleaved = interleave ( concatenate ( std : : move ( data ) ) ) ;
const Containers : : StridedArrayView2D < char > vertexData = MeshTools : : interleavedMutableData ( ownedInterleaved ) ;
UnsignedInt uniqueVertexCount ;
Containers : : Array < char > indexData ;
MeshIndexType indexType ;
if ( ownedInterleaved . isIndexed ( ) ) {
uniqueVertexCount = removeDuplicatesIndexedInPlace ( ownedInterleaved . mutableIndices ( ) , vertexData ) ;
indexData = ownedInterleaved . releaseIndexData ( ) ;
indexType = ownedInterleaved . indexType ( ) ;
} else {
indexData = Containers : : Array < char > { Containers : : NoInit , ownedInterleaved . vertexCount ( ) * sizeof ( UnsignedInt ) } ;
uniqueVertexCount = removeDuplicatesInPlaceInto ( vertexData , Containers : : arrayCast < UnsignedInt > ( indexData ) ) ;
indexType = MeshIndexType : : UnsignedInt ;
}
/* Allocate a new, shorter vertex data and copy the prefix */
/** @todo better idea? even if we would use growable arrays in duplicate()
or interleave ( ) above , arrayResize ( ) wouldn ' t release the excessive
memory in any way . This is basically equivalent to STL ' s
shrink_to_fit ( ) , which also copies */
Containers : : Array < char > uniqueVertexData { Containers : : NoInit , uniqueVertexCount * vertexData . size ( ) [ 1 ] } ;
Utility : : copy ( vertexData . prefix ( uniqueVertexCount ) ,
Containers : : StridedArrayView2D < char > { uniqueVertexData , { uniqueVertexCount , vertexData . size ( ) [ 1 ] } } ) ;
/* Route all attributes to the new vertex data */
Containers : : Array < Trade : : MeshAttributeData > attributeData { ownedInterleaved . attributeCount ( ) } ;
for ( UnsignedInt i = 0 ; i ! = ownedInterleaved . attributeCount ( ) ; + + i )
attributeData [ i ] = Trade : : MeshAttributeData { ownedInterleaved . attributeName ( i ) ,
ownedInterleaved . attributeFormat ( i ) ,
ownedInterleaved . attributeArraySize ( i ) ,
Containers : : StridedArrayView1D < void > { uniqueVertexData ,
uniqueVertexData . data ( ) + ownedInterleaved . attributeOffset ( i ) ,
uniqueVertexCount ,
ownedInterleaved . attributeStride ( i ) } } ;
Trade : : MeshIndexData indices { indexType , indexData } ;
return Trade : : MeshData { ownedInterleaved . primitive ( ) ,
std : : move ( indexData ) , indices ,
std : : move ( uniqueVertexData ) , std : : move ( attributeData ) ,
uniqueVertexCount } ;
}
} }