- Update XML output written by assimp dump. cameras & lights missing, document scheme as well. No guarantees that I won't change it in future without further notice (currently WIP, format to be freezed with our next release).
- Add dump comparison tool to assimp_cmd. It serves as the workhorse of the regression suite. git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@598 67173fc5-114c-0410-ac8e-9d2fd5bffc1fpull/1/head
parent
30ca88e782
commit
ff53e84749
|
@ -1 +1,894 @@
|
||||||
|
/*
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
Open Asset Import Library (ASSIMP)
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (c) 2006-2008, ASSIMP Development Team
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
with or without modification, are permitted provided that the following
|
||||||
|
conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above
|
||||||
|
copyright notice, this list of conditions and the
|
||||||
|
following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the
|
||||||
|
following disclaimer in the documentation and/or other
|
||||||
|
materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the ASSIMP team, nor the names of its
|
||||||
|
contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior
|
||||||
|
written permission of the ASSIMP Development Team.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file CompareDump.cpp
|
||||||
|
* @brief Implementation of the 'assimp cmpdmp', which compares
|
||||||
|
* two model dumps for equality. It plays an important role
|
||||||
|
* in the regression test suite.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Main.h"
|
||||||
|
const char* AICMD_MSG_CMPDUMP_HELP =
|
||||||
|
"assimp cmpdump <actual> <expected>\n"
|
||||||
|
"\tCompare two short dumps produced with \'assimp dump <..> -s\' for equality.\n"
|
||||||
|
;
|
||||||
|
|
||||||
|
#include "../../code/assbin_chunks.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#include "generic_inserter.hpp"
|
||||||
|
|
||||||
|
// get << for aiString
|
||||||
|
template <typename char_t, typename traits_t>
|
||||||
|
void mysprint(std::basic_ostream<char_t, traits_t>& os, const aiString& vec) {
|
||||||
|
os << "[length: \'" << std::dec << vec.length << "\' content: \'" << vec.data << "\']";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename char_t, typename traits_t>
|
||||||
|
std::basic_ostream<char_t, traits_t>& operator<< (std::basic_ostream<char_t, traits_t>& os, const aiString& vec) {
|
||||||
|
return generic_inserter(mysprint<char_t,traits_t>, os, vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
class sliced_chunk_iterator;
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @class compare_fails_exception
|
||||||
|
///
|
||||||
|
/// @brief Sentinel exception to return quickly from deeply nested control paths
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
class compare_fails_exception : public virtual std::exception {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum {MAX_ERR_LEN = 4096};
|
||||||
|
|
||||||
|
/* public c'tors */
|
||||||
|
compare_fails_exception(const char* msg) {
|
||||||
|
strncpy(mywhat,msg,MAX_ERR_LEN-1);
|
||||||
|
strcat(mywhat,"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public member functions */
|
||||||
|
const char* what() const throw() {
|
||||||
|
return mywhat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
char mywhat[MAX_ERR_LEN+1];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define MY_FLT_EPSILON 1e-1f
|
||||||
|
#define MY_DBL_EPSILON 1e-1
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @class comparer_context
|
||||||
|
///
|
||||||
|
/// @brief Record our way through the files to be compared and dump useful information if we fail.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
class comparer_context {
|
||||||
|
friend class sliced_chunk_iterator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/* construct given two file handles to compare */
|
||||||
|
comparer_context(FILE* actual,FILE* expect)
|
||||||
|
: actual(actual)
|
||||||
|
, expect(expect)
|
||||||
|
, cnt_chunks(0)
|
||||||
|
{
|
||||||
|
assert(actual);
|
||||||
|
assert(expect);
|
||||||
|
|
||||||
|
fseek(actual,0,SEEK_END);
|
||||||
|
lengths.push(std::make_pair(static_cast<uint32_t>(ftell(actual)),0));
|
||||||
|
fseek(actual,0,SEEK_SET);
|
||||||
|
|
||||||
|
history.push_back(HistoryEntry("---",PerChunkCounter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
|
||||||
|
/* set new scope */
|
||||||
|
void push_elem(const char* msg) {
|
||||||
|
const std::string s = msg;
|
||||||
|
|
||||||
|
PerChunkCounter::const_iterator it = history.back().second.find(s);
|
||||||
|
if(it != history.back().second.end()) {
|
||||||
|
++history.back().second[s];
|
||||||
|
}
|
||||||
|
else history.back().second[s] = 1;
|
||||||
|
|
||||||
|
history.push_back(HistoryEntry(s,PerChunkCounter()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* leave current scope */
|
||||||
|
void pop_elem() {
|
||||||
|
assert(history.size());
|
||||||
|
history.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* push current chunk length and start offset on top of stack */
|
||||||
|
void push_length(uint32_t nl, uint32_t start) {
|
||||||
|
lengths.push(std::make_pair(nl,start));
|
||||||
|
++cnt_chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pop the chunk length stack */
|
||||||
|
void pop_length() {
|
||||||
|
assert(lengths.size());
|
||||||
|
lengths.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* access the current chunk length */
|
||||||
|
uint32_t get_latest_chunk_length() {
|
||||||
|
assert(lengths.size());
|
||||||
|
return lengths.top().first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* access the current chunk start offset */
|
||||||
|
uint32_t get_latest_chunk_start() {
|
||||||
|
assert(lengths.size());
|
||||||
|
return lengths.top().second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* total number of chunk headers passed so far*/
|
||||||
|
uint32_t get_num_chunks() {
|
||||||
|
return cnt_chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* get ACTUAL file desc. != NULL */
|
||||||
|
FILE* get_actual() const {
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get EXPECT file desc. != NULL */
|
||||||
|
FILE* get_expect() const {
|
||||||
|
return expect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* compare next T from both streams, name occurs in error messages */
|
||||||
|
template<typename T> T cmp(const std::string& name) {
|
||||||
|
T a,e;
|
||||||
|
read(a,e);
|
||||||
|
|
||||||
|
if(a != e) {
|
||||||
|
std::stringstream ss;
|
||||||
|
failure((ss<< "Expected " << e << ", but actual is " << a,
|
||||||
|
ss.str()),name);
|
||||||
|
}
|
||||||
|
// std::cout << name << " " << std::hex << a << std::endl;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compare next num T's from both streams, name occurs in error messages */
|
||||||
|
template<typename T> void cmp(size_t num,const std::string& name) {
|
||||||
|
for(size_t n = 0; n < num; ++n) {
|
||||||
|
std::stringstream ss;
|
||||||
|
cmp<T>((ss<<name<<"["<<n<<"]",ss.str()));
|
||||||
|
// std::cout << name << " " << std::hex << a << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bounds of an aiVector3D array (separate function
|
||||||
|
* because partial specializations of member functions are illegal--)*/
|
||||||
|
template<typename T> void cmp_bounds(const std::string& name) {
|
||||||
|
cmp<T> (name+".<minimum-value>");
|
||||||
|
cmp<T> (name+".<maximum-value>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/* Report failure */
|
||||||
|
void failure(const std::string& err, const std::string& name) {
|
||||||
|
std::stringstream ss;
|
||||||
|
throw compare_fails_exception((ss
|
||||||
|
<< "Files are different at "
|
||||||
|
<< history.back().first
|
||||||
|
<< "."
|
||||||
|
<< name
|
||||||
|
<< ".\nError is: "
|
||||||
|
<< err
|
||||||
|
<< ".\nCurrent position in scene hierarchy is "
|
||||||
|
<< print_hierarchy(),ss.str().c_str()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** print our 'stack' */
|
||||||
|
std::string print_hierarchy() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::endl;
|
||||||
|
|
||||||
|
const char* last = history.back().first.c_str();
|
||||||
|
std::string pad;
|
||||||
|
|
||||||
|
for(ChunkHistory::reverse_iterator rev = ++history.rbegin(),
|
||||||
|
end = history.rend(); rev < end; ++rev, pad += " ")
|
||||||
|
{
|
||||||
|
ss << pad << (*rev).first << "(Index: " << (*rev).second[last]-1 << ")" << std::endl;
|
||||||
|
last = (*rev).first.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* read from both streams simult.*/
|
||||||
|
template <typename T> void read(T& filla,T& fille) {
|
||||||
|
if(1 != fread(&filla,sizeof(T),1,actual)) {
|
||||||
|
throw compare_fails_exception("Unexpected EOF reading ACTUAL");
|
||||||
|
}
|
||||||
|
if(1 != fread(&fille,sizeof(T),1,expect)) {
|
||||||
|
throw compare_fails_exception("Unexpected EOF reading EXPECT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FILE *const actual, *const expect;
|
||||||
|
|
||||||
|
typedef std::map<std::string,unsigned int> PerChunkCounter;
|
||||||
|
typedef std::pair<std::string,PerChunkCounter> HistoryEntry;
|
||||||
|
|
||||||
|
typedef std::deque<HistoryEntry> ChunkHistory;
|
||||||
|
ChunkHistory history;
|
||||||
|
|
||||||
|
typedef std::stack<std::pair<uint32_t,uint32_t> > LengthStack;
|
||||||
|
LengthStack lengths;
|
||||||
|
|
||||||
|
uint32_t cnt_chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* specialization for aiString (it needs separate handling because its on-disk representation
|
||||||
|
* differs from its binary representation in memory and can't be treated as an array of n T's.*/
|
||||||
|
template <> void comparer_context :: read<aiString>(aiString& filla,aiString& fille) {
|
||||||
|
uint32_t lena,lene;
|
||||||
|
read(lena,lene);
|
||||||
|
|
||||||
|
if(lena && 1 != fread(&filla.data,lena,1,actual)) {
|
||||||
|
throw compare_fails_exception("Unexpected EOF reading ACTUAL");
|
||||||
|
}
|
||||||
|
if(lene && 1 != fread(&fille.data,lene,1,expect)) {
|
||||||
|
throw compare_fails_exception("Unexpected EOF reading ACTUAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
fille.data[fille.length=static_cast<unsigned int>(lene)] = '\0';
|
||||||
|
filla.data[filla.length=static_cast<unsigned int>(lena)] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for float, uses epsilon for comparisons*/
|
||||||
|
template<> float comparer_context :: cmp<float>(const std::string& name)
|
||||||
|
{
|
||||||
|
float a,e,t;
|
||||||
|
read(a,e);
|
||||||
|
|
||||||
|
if((t=fabs(a-e)) > MY_FLT_EPSILON) {
|
||||||
|
std::stringstream ss;
|
||||||
|
failure((ss<< "Expected " << e << ", but actual is "
|
||||||
|
<< a << " (delta is " << t << ")", ss.str()),name);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for double, uses epsilon for comparisons*/
|
||||||
|
template<> double comparer_context :: cmp<double>(const std::string& name)
|
||||||
|
{
|
||||||
|
double a,e,t;
|
||||||
|
read(a,e);
|
||||||
|
|
||||||
|
if((t=fabs(a-e)) > MY_DBL_EPSILON) {
|
||||||
|
std::stringstream ss;
|
||||||
|
failure((ss<< "Expected " << e << ", but actual is "
|
||||||
|
<< a << " (delta is " << t << ")", ss.str()),name);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiVector3D */
|
||||||
|
template<> aiVector3D comparer_context :: cmp<aiVector3D >(const std::string& name)
|
||||||
|
{
|
||||||
|
const float x = cmp<float>(name+".x");
|
||||||
|
const float y = cmp<float>(name+".y");
|
||||||
|
const float z = cmp<float>(name+".z");
|
||||||
|
|
||||||
|
return aiVector3D(x,y,z);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiColor4D */
|
||||||
|
template<> aiColor4D comparer_context :: cmp<aiColor4D >(const std::string& name)
|
||||||
|
{
|
||||||
|
const float r = cmp<float>(name+".r");
|
||||||
|
const float g = cmp<float>(name+".g");
|
||||||
|
const float b = cmp<float>(name+".b");
|
||||||
|
const float a = cmp<float>(name+".a");
|
||||||
|
|
||||||
|
return aiColor4D(r,g,b,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiQuaternion */
|
||||||
|
template<> aiQuaternion comparer_context :: cmp<aiQuaternion >(const std::string& name)
|
||||||
|
{
|
||||||
|
const float w = cmp<float>(name+".w");
|
||||||
|
const float x = cmp<float>(name+".x");
|
||||||
|
const float y = cmp<float>(name+".y");
|
||||||
|
const float z = cmp<float>(name+".z");
|
||||||
|
|
||||||
|
return aiQuaternion(w,x,y,z);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiQuatKey */
|
||||||
|
template<> aiQuatKey comparer_context :: cmp<aiQuatKey >(const std::string& name)
|
||||||
|
{
|
||||||
|
const double mTime = cmp<double>(name+".mTime");
|
||||||
|
const aiQuaternion mValue = cmp<aiQuaternion>(name+".mValue");
|
||||||
|
|
||||||
|
return aiQuatKey(mTime,mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiVectorKey */
|
||||||
|
template<> aiVectorKey comparer_context :: cmp<aiVectorKey >(const std::string& name)
|
||||||
|
{
|
||||||
|
const double mTime = cmp<double>(name+".mTime");
|
||||||
|
const aiVector3D mValue = cmp<aiVector3D>(name+".mValue");
|
||||||
|
|
||||||
|
return aiVectorKey(mTime,mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiMatrix4x4 */
|
||||||
|
template<> aiMatrix4x4 comparer_context :: cmp<aiMatrix4x4 >(const std::string& name)
|
||||||
|
{
|
||||||
|
aiMatrix4x4 res;
|
||||||
|
for(unsigned int i = 0; i < 4; ++i) {
|
||||||
|
for(unsigned int j = 0; j < 4; ++j) {
|
||||||
|
std::stringstream ss;
|
||||||
|
res[i][j] = cmp<float>(name+(ss<<".m"<<i<<j,ss.str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Specialization for aiVertexWeight */
|
||||||
|
template<> aiVertexWeight comparer_context :: cmp<aiVertexWeight >(const std::string& name)
|
||||||
|
{
|
||||||
|
const unsigned int mVertexId = cmp<unsigned int>(name+".mVertexId");
|
||||||
|
const float mWeight = cmp<float>(name+".mWeight");
|
||||||
|
|
||||||
|
return aiVertexWeight(mVertexId,mWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @class sliced_chunk_iterator
|
||||||
|
///
|
||||||
|
/// @brief Helper to iterate easily through corresponding chunks of two dumps simultaneously.
|
||||||
|
///
|
||||||
|
/// Not a *real* iterator, doesn't fully conform to the isocpp iterator spec
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
class sliced_chunk_iterator {
|
||||||
|
|
||||||
|
friend class sliced_chunk_reader;
|
||||||
|
sliced_chunk_iterator(comparer_context& ctx, long end)
|
||||||
|
: ctx(ctx)
|
||||||
|
, endit(false)
|
||||||
|
, next(std::numeric_limits<long>::max())
|
||||||
|
, end(end)
|
||||||
|
{
|
||||||
|
load_next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
~sliced_chunk_iterator() {
|
||||||
|
fseek(ctx.get_actual(),end,SEEK_SET);
|
||||||
|
fseek(ctx.get_expect(),end,SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/* get current chunk head */
|
||||||
|
typedef std::pair<uint32_t,uint32_t> Chunk;
|
||||||
|
const Chunk& operator*() {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get to next chunk head */
|
||||||
|
const sliced_chunk_iterator& operator++() {
|
||||||
|
cleanup();
|
||||||
|
load_next();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* */
|
||||||
|
bool is_end() const {
|
||||||
|
return endit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/* get to the end of *this* chunk */
|
||||||
|
void cleanup() {
|
||||||
|
if(next != std::numeric_limits<long>::max()) {
|
||||||
|
fseek(ctx.get_actual(),next,SEEK_SET);
|
||||||
|
fseek(ctx.get_expect(),next,SEEK_SET);
|
||||||
|
|
||||||
|
ctx.pop_length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* advance to the next chunk */
|
||||||
|
void load_next() {
|
||||||
|
|
||||||
|
Chunk actual;
|
||||||
|
size_t res=0;
|
||||||
|
|
||||||
|
const long cur = ftell(ctx.get_expect());
|
||||||
|
if(end-cur<8) {
|
||||||
|
current = std::make_pair(0u,0u);
|
||||||
|
endit = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res|=fread(¤t.first,4,1,ctx.get_expect());
|
||||||
|
res|=fread(¤t.second,4,1,ctx.get_expect()) <<1u;
|
||||||
|
res|=fread(&actual.first,4,1,ctx.get_actual()) <<2u;
|
||||||
|
res|=fread(&actual.second,4,1,ctx.get_actual()) <<3u;
|
||||||
|
|
||||||
|
if(res!=0xf) {
|
||||||
|
ctx.failure("I/OError reading chunk head, dumps are not well-defined","<ChunkHead>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.first != actual.first) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ctx.failure((ss
|
||||||
|
<<"Chunk headers do not match. EXPECT: "
|
||||||
|
<< std::hex << current.first
|
||||||
|
<<" ACTUAL: "
|
||||||
|
<< /*std::hex */actual.first,
|
||||||
|
ss.str()),
|
||||||
|
"<ChunkHead>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.first != actual.first) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ctx.failure((ss
|
||||||
|
<<"Chunk lenghts do not match. EXPECT: "
|
||||||
|
<<current.second
|
||||||
|
<<" ACTUAL: "
|
||||||
|
<< actual.second,
|
||||||
|
ss.str()),
|
||||||
|
"<ChunkHead>");
|
||||||
|
}
|
||||||
|
|
||||||
|
next = cur+current.second+8;
|
||||||
|
ctx.push_length(current.second,cur+8);
|
||||||
|
}
|
||||||
|
|
||||||
|
comparer_context& ctx;
|
||||||
|
Chunk current;
|
||||||
|
bool endit;
|
||||||
|
long next,end;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @class sliced_chunk_reader
|
||||||
|
///
|
||||||
|
/// @brief Helper to iterate easily through corresponding chunks of two dumps simultaneously.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
class sliced_chunk_reader {
|
||||||
|
public:
|
||||||
|
|
||||||
|
//
|
||||||
|
sliced_chunk_reader(comparer_context& ctx)
|
||||||
|
: ctx(ctx)
|
||||||
|
{}
|
||||||
|
|
||||||
|
//
|
||||||
|
~sliced_chunk_reader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sliced_chunk_iterator begin() const {
|
||||||
|
return sliced_chunk_iterator(ctx,ctx.get_latest_chunk_length()+
|
||||||
|
ctx.get_latest_chunk_start());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
comparer_context& ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// @class scoped_chunk
|
||||||
|
///
|
||||||
|
/// @brief Utility to simplify usage of comparer_context.push_elem/pop_elem
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
class scoped_chunk {
|
||||||
|
public:
|
||||||
|
|
||||||
|
//
|
||||||
|
scoped_chunk(comparer_context& ctx,const char* msg)
|
||||||
|
: ctx(ctx)
|
||||||
|
{
|
||||||
|
ctx.push_elem(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
~scoped_chunk()
|
||||||
|
{
|
||||||
|
ctx.pop_elem();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
comparer_context& ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyMaterialProperty(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiMaterialProperty");
|
||||||
|
|
||||||
|
comp.cmp<aiString>("mKey");
|
||||||
|
comp.cmp<uint32_t>("mSemantic");
|
||||||
|
comp.cmp<uint32_t>("mIndex");
|
||||||
|
const uint32_t length = comp.cmp<uint32_t>("mDataLength");
|
||||||
|
const aiPropertyTypeInfo type = static_cast<aiPropertyTypeInfo>(
|
||||||
|
comp.cmp<uint32_t>("mType"));
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case aiPTI_Float:
|
||||||
|
comp.cmp<float>(length/4,"mData");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case aiPTI_String:
|
||||||
|
comp.cmp<aiString>("mData");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case aiPTI_Integer:
|
||||||
|
comp.cmp<uint32_t>(length/4,"mData");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case aiPTI_Buffer:
|
||||||
|
comp.cmp<uint8_t>(length,"mData");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyMaterial(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiMaterial");
|
||||||
|
|
||||||
|
comp.cmp<uint32_t>("aiMaterial::mNumProperties");
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AIMATERIALPROPERTY) {
|
||||||
|
CompareOnTheFlyMaterialProperty(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyBone(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiBone");
|
||||||
|
comp.cmp<aiString>("mName");
|
||||||
|
comp.cmp<uint32_t>("mNumWeights");
|
||||||
|
comp.cmp<aiMatrix4x4>("mOffsetMatrix");
|
||||||
|
|
||||||
|
comp.cmp_bounds<aiVertexWeight>("mWeights");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyNodeAnim(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiNodeAnim");
|
||||||
|
|
||||||
|
comp.cmp<aiString>("mNodeName");
|
||||||
|
comp.cmp<uint32_t>("mNumPositionKeys");
|
||||||
|
comp.cmp<uint32_t>("mNumRotationKeys");
|
||||||
|
comp.cmp<uint32_t>("mNumScalingKeys");
|
||||||
|
comp.cmp<uint32_t>("mPreState");
|
||||||
|
comp.cmp<uint32_t>("mPostState");
|
||||||
|
|
||||||
|
comp.cmp_bounds<aiVector3D>("mPositionKeys");
|
||||||
|
comp.cmp_bounds<aiVector3D>("mRotationKeys");
|
||||||
|
comp.cmp_bounds<aiVector3D>("mScalingKeys");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyMesh(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiMesh");
|
||||||
|
|
||||||
|
comp.cmp<uint32_t>("mPrimitiveTypes");
|
||||||
|
comp.cmp<uint32_t>("mNumVertices");
|
||||||
|
const uint32_t nf = comp.cmp<uint32_t>("mNumFaces");
|
||||||
|
comp.cmp<uint32_t>("mNumBones");
|
||||||
|
comp.cmp<uint32_t>("mMaterialIndex");
|
||||||
|
|
||||||
|
const uint32_t present = comp.cmp<uint32_t>("<vertex-components-present>");
|
||||||
|
if(present & ASSBIN_MESH_HAS_POSITIONS) {
|
||||||
|
comp.cmp_bounds<aiVector3D>("mVertices");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(present & ASSBIN_MESH_HAS_NORMALS) {
|
||||||
|
comp.cmp_bounds<aiVector3D>("mNormals");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(present & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) {
|
||||||
|
comp.cmp_bounds<aiVector3D>("mTangents");
|
||||||
|
comp.cmp_bounds<aiVector3D>("mBitangents");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i = 0; present & ASSBIN_MESH_HAS_COLOR(i); ++i) {
|
||||||
|
std::stringstream ss;
|
||||||
|
comp.cmp_bounds<aiColor4D>((ss<<"mColors["<<i<<"]",ss.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i = 0; present & ASSBIN_MESH_HAS_TEXCOORD(i); ++i) {
|
||||||
|
std::stringstream ss;
|
||||||
|
comp.cmp<uint32_t>((ss<<"mNumUVComponents["<<i<<"]",ss.str()));
|
||||||
|
comp.cmp_bounds<aiVector3D>((ss.clear(),ss<<"mTextureCoords["<<i<<"]",ss.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i< ((nf+511)/512); ++i) {
|
||||||
|
std::stringstream ss;
|
||||||
|
comp.cmp<uint32_t>((ss<<"mFaces["<<i*512<<"-"<<std::min(static_cast<
|
||||||
|
uint32_t>((i+1)*512),nf)<<"]",ss.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AIBONE) {
|
||||||
|
CompareOnTheFlyBone(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyCamera(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiCamera");
|
||||||
|
|
||||||
|
comp.cmp<aiString>("mName");
|
||||||
|
|
||||||
|
comp.cmp<aiVector3D>("mPosition");
|
||||||
|
comp.cmp<aiVector3D>("mLookAt");
|
||||||
|
comp.cmp<aiVector3D>("mUp");
|
||||||
|
|
||||||
|
comp.cmp<float>("mHorizontalFOV");
|
||||||
|
comp.cmp<float>("mClipPlaneNear");
|
||||||
|
comp.cmp<float>("mClipPlaneFar");
|
||||||
|
comp.cmp<float>("mAspect");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyLight(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiLight");
|
||||||
|
|
||||||
|
comp.cmp<aiString>("mName");
|
||||||
|
const aiLightSourceType type = static_cast<aiLightSourceType>(
|
||||||
|
comp.cmp<uint32_t>("mType"));
|
||||||
|
|
||||||
|
if(type==aiLightSource_DIRECTIONAL) {
|
||||||
|
comp.cmp<float>("mAttenuationConstant");
|
||||||
|
comp.cmp<float>("mAttenuationLinear");
|
||||||
|
comp.cmp<float>("mAttenuationQuadratic");
|
||||||
|
}
|
||||||
|
|
||||||
|
comp.cmp<aiVector3D>("mColorDiffuse");
|
||||||
|
comp.cmp<aiVector3D>("mColorSpecular");
|
||||||
|
comp.cmp<aiVector3D>("mColorAmbient");
|
||||||
|
|
||||||
|
if(type==aiLightSource_SPOT) {
|
||||||
|
comp.cmp<float>("mAngleInnerCone");
|
||||||
|
comp.cmp<float>("mAngleOuterCone");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyAnimation(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiAnimation");
|
||||||
|
|
||||||
|
comp.cmp<aiString>("mName");
|
||||||
|
comp.cmp<double>("mDuration");
|
||||||
|
comp.cmp<double>("mTicksPerSecond");
|
||||||
|
comp.cmp<uint32_t>("mNumChannels");
|
||||||
|
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AINODEANIM) {
|
||||||
|
CompareOnTheFlyNodeAnim(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyTexture(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiTexture");
|
||||||
|
|
||||||
|
const uint32_t w = comp.cmp<uint32_t>("mWidth");
|
||||||
|
const uint32_t h = comp.cmp<uint32_t>("mHeight");
|
||||||
|
comp.cmp<char>("achFormatHint[0]");
|
||||||
|
comp.cmp<char>("achFormatHint[1]");
|
||||||
|
comp.cmp<char>("achFormatHint[2]");
|
||||||
|
comp.cmp<char>("achFormatHint[3]");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyNode(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiNode");
|
||||||
|
comp.cmp<aiString>("mName");
|
||||||
|
comp.cmp<aiMatrix4x4>("mTransformation");
|
||||||
|
comp.cmp<uint32_t>("mNumChildren");
|
||||||
|
comp.cmp<uint32_t>(comp.cmp<uint32_t>("mNumMeshes"),"mMeshes");
|
||||||
|
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AINODE) {
|
||||||
|
CompareOnTheFlyNode(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFlyScene(comparer_context& comp) {
|
||||||
|
scoped_chunk chunk(comp,"aiScene");
|
||||||
|
|
||||||
|
comp.cmp<uint32_t>("mFlags");
|
||||||
|
comp.cmp<uint32_t>("mNumMeshes");
|
||||||
|
comp.cmp<uint32_t>("mNumMaterials");
|
||||||
|
comp.cmp<uint32_t>("mNumAnimations");
|
||||||
|
comp.cmp<uint32_t>("mNumTextures");
|
||||||
|
comp.cmp<uint32_t>("mNumLights");
|
||||||
|
comp.cmp<uint32_t>("mNumCameras");
|
||||||
|
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AIMATERIAL) {
|
||||||
|
CompareOnTheFlyMaterial(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AITEXTURE) {
|
||||||
|
CompareOnTheFlyTexture(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AIMESH) {
|
||||||
|
CompareOnTheFlyMesh(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AIANIMATION) {
|
||||||
|
CompareOnTheFlyAnimation(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AICAMERA) {
|
||||||
|
CompareOnTheFlyCamera(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AILIGHT) {
|
||||||
|
CompareOnTheFlyLight(comp);
|
||||||
|
}
|
||||||
|
else if ((*it).first == ASSBIN_CHUNK_AINODE) {
|
||||||
|
CompareOnTheFlyNode(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CompareOnTheFly(comparer_context& comp)
|
||||||
|
{
|
||||||
|
sliced_chunk_reader reader(comp);
|
||||||
|
for(sliced_chunk_iterator it = reader.begin(); !it.is_end(); ++it) {
|
||||||
|
if ((*it).first == ASSBIN_CHUNK_AISCENE) {
|
||||||
|
CompareOnTheFlyScene(comp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void CheckHeader(comparer_context& comp)
|
||||||
|
{
|
||||||
|
fseek(comp.get_actual(),ASSBIN_HEADER_LENGTH,SEEK_CUR);
|
||||||
|
fseek(comp.get_expect(),ASSBIN_HEADER_LENGTH,SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
int Assimp_CompareDump (const char* const* params, unsigned int num)
|
||||||
|
{
|
||||||
|
// --help
|
||||||
|
if (num == 1 && !strcmp( params[0], "-h") || !strcmp( params[0], "--help") || !strcmp( params[0], "-?") ) {
|
||||||
|
printf("%s",AICMD_MSG_CMPDUMP_HELP);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assimp cmpdump actual expected
|
||||||
|
if (num < 1) {
|
||||||
|
std::cout << "assimp cmpdump: Invalid number of arguments. "
|
||||||
|
"See \'assimp cmpdump --help\'\r\n" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!strcmp(params[0],params[1])) {
|
||||||
|
std::cout << "assimp cmpdump: same file, same content." << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* actual = fopen(params[0],"rb"), *expected = fopen(params[1],"rb");
|
||||||
|
if (!actual) {
|
||||||
|
std::cout << "assimp cmpdump: Failure reading ACTUAL data from " <<
|
||||||
|
params[0] << std::endl;
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
|
if (!expected) {
|
||||||
|
std::cout << "assimp cmpdump: Failure reading EXPECT data from " <<
|
||||||
|
params[1] << std::endl;
|
||||||
|
return -6;
|
||||||
|
}
|
||||||
|
|
||||||
|
comparer_context comp(actual,expected);
|
||||||
|
try {
|
||||||
|
CheckHeader(comp);
|
||||||
|
CompareOnTheFly(comp);
|
||||||
|
}
|
||||||
|
catch(const compare_fails_exception& ex) {
|
||||||
|
printf("%s",ex.what());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
// we don't bother checking too rigourously here, so
|
||||||
|
// we might end up here ...
|
||||||
|
std::cout << "Unknown failure, are the input files well-defined?";
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Success (totally " << std::dec << comp.get_num_chunks() <<
|
||||||
|
" chunks)" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -46,11 +46,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "Main.h"
|
#include "Main.h"
|
||||||
|
|
||||||
const char* AICMD_MSG_DUMP_HELP_E =
|
const char* AICMD_MSG_DUMP_HELP_E =
|
||||||
"todo assimp extract help";
|
"assimp extract <model> [<out>] [-t<n>] [-f<fmt>] [-ba] [-s] [common parameters]\n"
|
||||||
|
"\t -ba Writes BMP's with alpha channel\n"
|
||||||
|
"\t -t<n> Zero-based index of the texture to be extracted \n"
|
||||||
|
"\t -f<f> Specify the file format if <out> is ommitted \n"
|
||||||
|
"\t[See the assimp_cmd docs for a full list of all common parameters] \n"
|
||||||
|
"\t -cfast Fast post processing preset, runs just a few important steps \n"
|
||||||
|
"\t -cdefault Default post processing: runs all recommended steps\n"
|
||||||
|
"\t -cfull Fires almost all post processing steps \n"
|
||||||
|
;
|
||||||
|
|
||||||
#define AI_EXTRACT_WRITE_BMP_ALPHA 0x1
|
#define AI_EXTRACT_WRITE_BMP_ALPHA 0x1
|
||||||
|
|
||||||
#include "Compiler/pushpack1.h"
|
#include "Compiler/pushpack1.h"
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
@ -136,7 +142,7 @@ int SaveAsBMP (FILE* file, const aiTexel* data, unsigned int width, unsigned int
|
||||||
header.bfSize = header.bfOffBits+width*height*numc;
|
header.bfSize = header.bfOffBits+width*height*numc;
|
||||||
header.bfReserved1 = header.bfReserved2 = 0;
|
header.bfReserved1 = header.bfReserved2 = 0;
|
||||||
|
|
||||||
::fwrite(&header,sizeof(BITMAPFILEHEADER),1,file);
|
fwrite(&header,sizeof(BITMAPFILEHEADER),1,file);
|
||||||
|
|
||||||
BITMAPINFOHEADER info;
|
BITMAPINFOHEADER info;
|
||||||
info.biSize = 40;
|
info.biSize = 40;
|
||||||
|
@ -151,13 +157,13 @@ int SaveAsBMP (FILE* file, const aiTexel* data, unsigned int width, unsigned int
|
||||||
info.biClrUsed = 0;
|
info.biClrUsed = 0;
|
||||||
info.biClrImportant = 0;
|
info.biClrImportant = 0;
|
||||||
|
|
||||||
::fwrite(&info,sizeof(BITMAPINFOHEADER),1,file);
|
fwrite(&info,sizeof(BITMAPINFOHEADER),1,file);
|
||||||
|
|
||||||
unsigned char* temp = buffer+info.biSizeImage;
|
unsigned char* temp = buffer+info.biSizeImage;
|
||||||
const unsigned int row = width*numc;
|
const unsigned int row = width*numc;
|
||||||
|
|
||||||
for (int y = 0; temp -= row,y < info.biHeight;++y) {
|
for (int y = 0; temp -= row,y < info.biHeight;++y) {
|
||||||
::fwrite(temp,row,1,file);
|
fwrite(temp,row,1,file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the buffer
|
// delete the buffer
|
||||||
|
@ -179,11 +185,11 @@ int SaveAsTGA (FILE* file, const aiTexel* data, unsigned int width, unsigned int
|
||||||
head.descriptor |= (1u<<5);
|
head.descriptor |= (1u<<5);
|
||||||
|
|
||||||
head.imagetype = 2; // actually it's RGBA
|
head.imagetype = 2; // actually it's RGBA
|
||||||
::fwrite(&head,sizeof(TGA_HEADER),1,file);
|
fwrite(&head,sizeof(TGA_HEADER),1,file);
|
||||||
|
|
||||||
for (unsigned int y = 0; y < height; ++y) {
|
for (unsigned int y = 0; y < height; ++y) {
|
||||||
for (unsigned int x = 0; x < width; ++x) {
|
for (unsigned int x = 0; x < width; ++x) {
|
||||||
::fwrite(data + y*width+x,4,1,file);
|
fwrite(data + y*width+x,4,1,file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +218,7 @@ int DoExport(const aiTexture* tx, FILE* p, const std::string& extension,
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
// Implementation of the assimp extract utility
|
// Implementation of the assimp extract utility
|
||||||
int Assimp_Extract (const char** params, unsigned int num)
|
int Assimp_Extract (const char* const* params, unsigned int num)
|
||||||
{
|
{
|
||||||
if (num < 1) {
|
if (num < 1) {
|
||||||
printf("assimp extract: Invalid number of arguments. See \'assimp extract --help\'\n");
|
printf("assimp extract: Invalid number of arguments. See \'assimp extract --help\'\n");
|
||||||
|
@ -220,7 +226,7 @@ int Assimp_Extract (const char** params, unsigned int num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --help
|
// --help
|
||||||
if (!::strcmp( params[0], "-h") || !::strcmp( params[0], "--help") || !::strcmp( params[0], "-?") ) {
|
if (!strcmp( params[0], "-h") || !strcmp( params[0], "--help") || !strcmp( params[0], "-?") ) {
|
||||||
printf("%s",AICMD_MSG_DUMP_HELP_E);
|
printf("%s",AICMD_MSG_DUMP_HELP_E);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -246,28 +252,30 @@ int Assimp_Extract (const char** params, unsigned int num)
|
||||||
for (unsigned int i = (out[0] == '-' ? 1 : 2); i < num;++i) {
|
for (unsigned int i = (out[0] == '-' ? 1 : 2); i < num;++i) {
|
||||||
if (!params[i])continue;
|
if (!params[i])continue;
|
||||||
|
|
||||||
if (!::strncmp( params[i], "-f",2)) {
|
if (!strncmp( params[i], "-f",2)) {
|
||||||
extension = std::string(params[i]+2);
|
extension = std::string(params[i]+2);
|
||||||
}
|
}
|
||||||
else if ( !::strncmp( params[i], "--format=",9)) {
|
else if ( !strncmp( params[i], "--format=",9)) {
|
||||||
extension = std::string(params[i]+9);
|
extension = std::string(params[i]+9);
|
||||||
}
|
}
|
||||||
else if ( !::strcmp( params[i], "--nosuffix") || !::strcmp(params[i],"-s")) {
|
else if ( !strcmp( params[i], "--nosuffix") || !strcmp(params[i],"-s")) {
|
||||||
nosuffix = true;
|
nosuffix = true;
|
||||||
}
|
}
|
||||||
else if ( !::strncmp( params[i], "--texture=",10)) {
|
else if ( !strncmp( params[i], "--texture=",10)) {
|
||||||
texIdx = ::strtol10(params[i]+10);
|
texIdx = ::strtol10(params[i]+10);
|
||||||
}
|
}
|
||||||
else if ( !::strncmp( params[i], "-t",2)) {
|
else if ( !strncmp( params[i], "-t",2)) {
|
||||||
texIdx = ::strtol10(params[i]+2);
|
texIdx = ::strtol10(params[i]+2);
|
||||||
}
|
}
|
||||||
else if ( !::strcmp( params[i], "-ba") || !::strcmp( params[i], "--bmp-with-alpha")) {
|
else if ( !strcmp( params[i], "-ba") || !strcmp( params[i], "--bmp-with-alpha")) {
|
||||||
flags |= AI_EXTRACT_WRITE_BMP_ALPHA;
|
flags |= AI_EXTRACT_WRITE_BMP_ALPHA;
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
else {
|
else {
|
||||||
printf("Unknown parameter: %s\n",params[i]);
|
printf("Unknown parameter: %s\n",params[i]);
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
for (std::string::iterator it = extension.begin();it != extension.end();++it)
|
for (std::string::iterator it = extension.begin();it != extension.end();++it)
|
||||||
*it = ::tolower(*it);
|
*it = ::tolower(*it);
|
||||||
|
@ -310,10 +318,10 @@ int Assimp_Extract (const char** params, unsigned int num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now write all output textures
|
// now write all output textures
|
||||||
for (unsigned int i = 0; i < scene->mNumTextures;++i)
|
for (unsigned int i = 0; i < scene->mNumTextures;++i) {
|
||||||
{
|
if (texIdx != 0xffffffff && texIdx != i) {
|
||||||
if (texIdx != 0xffffffff && texIdx != i)
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const aiTexture* tex = scene->mTextures[i];
|
const aiTexture* tex = scene->mTextures[i];
|
||||||
std::string out_cpy = out, out_ext = extension;
|
std::string out_cpy = out, out_ext = extension;
|
||||||
|
@ -347,7 +355,7 @@ int Assimp_Extract (const char** params, unsigned int num)
|
||||||
int m;
|
int m;
|
||||||
|
|
||||||
if (!tex->mHeight) {
|
if (!tex->mHeight) {
|
||||||
m = (1 != ::fwrite(tex->pcData,tex->mWidth,1,p));
|
m = (1 != fwrite(tex->pcData,tex->mWidth,1,p));
|
||||||
}
|
}
|
||||||
else m = DoExport(tex,p,extension,flags);
|
else m = DoExport(tex,p,extension,flags);
|
||||||
::fclose(p);
|
::fclose(p);
|
||||||
|
|
|
@ -63,6 +63,7 @@ const char* AICMD_MSG_HELP =
|
||||||
"\t\tknowext - Check whether a file extension is recognized by Assimp\n"
|
"\t\tknowext - Check whether a file extension is recognized by Assimp\n"
|
||||||
"\t\textract - Extract an embedded texture from a model\n"
|
"\t\textract - Extract an embedded texture from a model\n"
|
||||||
"\t\tdump - Convert a model to binary or XML dumps (ASSBIN/ASSXML)\n"
|
"\t\tdump - Convert a model to binary or XML dumps (ASSBIN/ASSXML)\n"
|
||||||
|
"\t\tcmpdump - Compare two file dumps produced with \'assimp dump <file> -s ...\'\n"
|
||||||
"\n\n\tUse \'assimp <verb> --help\' to get detailed help for a command.\n"
|
"\n\n\tUse \'assimp <verb> --help\' to get detailed help for a command.\n"
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -73,14 +74,13 @@ const char* AICMD_MSG_HELP =
|
||||||
int main (int argc, char* argv[])
|
int main (int argc, char* argv[])
|
||||||
{
|
{
|
||||||
if (argc <= 1) {
|
if (argc <= 1) {
|
||||||
|
|
||||||
printf("assimp: No command specified. Use \'assimp help\' for a detailed command list\n");
|
printf("assimp: No command specified. Use \'assimp help\' for a detailed command list\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// assimp version
|
// assimp version
|
||||||
// Display version information
|
// Display version information
|
||||||
if (! ::strcmp(argv[1], "version")) {
|
if (! strcmp(argv[1], "version")) {
|
||||||
const unsigned int flags = aiGetCompileFlags();
|
const unsigned int flags = aiGetCompileFlags();
|
||||||
printf(AICMD_MSG_ABOUT,
|
printf(AICMD_MSG_ABOUT,
|
||||||
aiGetVersionMajor(),
|
aiGetVersionMajor(),
|
||||||
|
@ -103,7 +103,14 @@ int main (int argc, char* argv[])
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assimp cmpdump
|
||||||
|
// Compare two mini model dumps (regression suite)
|
||||||
|
if (! strcmp(argv[1], "cmpdump")) {
|
||||||
|
return Assimp_CompareDump (&argv[2],argc-2);
|
||||||
|
}
|
||||||
|
|
||||||
// construct a global Assimp::Importer instance
|
// construct a global Assimp::Importer instance
|
||||||
|
// because all further tools rely on it
|
||||||
Assimp::Importer imp;
|
Assimp::Importer imp;
|
||||||
globalImporter = &imp;
|
globalImporter = &imp;
|
||||||
|
|
||||||
|
@ -138,13 +145,13 @@ int main (int argc, char* argv[])
|
||||||
// assimp dump
|
// assimp dump
|
||||||
// Dump a model to a file
|
// Dump a model to a file
|
||||||
if (! strcmp(argv[1], "dump")) {
|
if (! strcmp(argv[1], "dump")) {
|
||||||
return Assimp_Dump ((const char**)&argv[2],argc-2);
|
return Assimp_Dump (&argv[2],argc-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// assimp extract
|
// assimp extract
|
||||||
// Extract an embedded texture from a file
|
// Extract an embedded texture from a file
|
||||||
if (! strcmp(argv[1], "extract")) {
|
if (! strcmp(argv[1], "extract")) {
|
||||||
return Assimp_Extract ((const char**)&argv[2],argc-2);
|
return Assimp_Extract (&argv[2],argc-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Unrecognized command. Use \'assimp help\' for a detailed command list\n");
|
printf("Unrecognized command. Use \'assimp help\' for a detailed command list\n");
|
||||||
|
@ -153,7 +160,9 @@ int main (int argc, char* argv[])
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Import a specific file
|
// Import a specific file
|
||||||
const aiScene* ImportModel(const ImportData& imp, const std::string& path)
|
const aiScene* ImportModel(
|
||||||
|
const ImportData& imp,
|
||||||
|
const std::string& path)
|
||||||
{
|
{
|
||||||
// Attach log streams
|
// Attach log streams
|
||||||
if (imp.log) {
|
if (imp.log) {
|
||||||
|
@ -172,12 +181,12 @@ const aiScene* ImportModel(const ImportData& imp, const std::string& path)
|
||||||
|
|
||||||
// Now validate this flag combination
|
// Now validate this flag combination
|
||||||
if(!globalImporter->ValidateFlags(imp.ppFlags)) {
|
if(!globalImporter->ValidateFlags(imp.ppFlags)) {
|
||||||
::printf("ERROR: Unsupported post-processing flags \n");
|
printf("ERROR: Unsupported post-processing flags \n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
printf("Validating postprocessing flags ... OK\n");
|
printf("Validating postprocessing flags ... OK\n");
|
||||||
if (imp.showLog)
|
if (imp.showLog)
|
||||||
::printf("-----------------------------------------------------------------\n");
|
printf("-----------------------------------------------------------------\n");
|
||||||
|
|
||||||
// do the actual import, measure time
|
// do the actual import, measure time
|
||||||
const clock_t first = clock();
|
const clock_t first = clock();
|
||||||
|
@ -205,7 +214,9 @@ const aiScene* ImportModel(const ImportData& imp, const std::string& path)
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Process standard arguments
|
// Process standard arguments
|
||||||
int ProcessStandardArguments(ImportData& fill, const char** params,
|
int ProcessStandardArguments(
|
||||||
|
ImportData& fill,
|
||||||
|
const char* const * params,
|
||||||
unsigned int num)
|
unsigned int num)
|
||||||
{
|
{
|
||||||
// -ptv --pretransform-vertices
|
// -ptv --pretransform-vertices
|
||||||
|
@ -237,11 +248,11 @@ int ProcessStandardArguments(ImportData& fill, const char** params,
|
||||||
|
|
||||||
for (unsigned int i = 0; i < num;++i)
|
for (unsigned int i = 0; i < num;++i)
|
||||||
{
|
{
|
||||||
if (!params[i]) { // could happen if some args have already been processed
|
//if (!params[i]) { // could happen if some args have already been processed
|
||||||
continue;
|
// continue;
|
||||||
}
|
//}
|
||||||
|
|
||||||
bool has = true;
|
// bool has = true;
|
||||||
if (! strcmp(params[i], "-ptv") || ! strcmp(params[i], "--pretransform-vertices")) {
|
if (! strcmp(params[i], "-ptv") || ! strcmp(params[i], "--pretransform-vertices")) {
|
||||||
fill.ppFlags |= aiProcess_PreTransformVertices;
|
fill.ppFlags |= aiProcess_PreTransformVertices;
|
||||||
}
|
}
|
||||||
|
@ -350,10 +361,10 @@ int ProcessStandardArguments(ImportData& fill, const char** params,
|
||||||
fill.logFile = "assimp-log.txt";
|
fill.logFile = "assimp-log.txt";
|
||||||
}
|
}
|
||||||
|
|
||||||
else has = false;
|
//else has = false;
|
||||||
if (has) {
|
//if (has) {
|
||||||
params[i] = NULL;
|
// params[i] = NULL;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fill.logFile.length() || fill.showLog || fill.verbose)
|
if (fill.logFile.length() || fill.showLog || fill.verbose)
|
||||||
|
|
|
@ -71,8 +71,7 @@ using namespace Assimp;
|
||||||
extern Assimp::Importer* globalImporter;
|
extern Assimp::Importer* globalImporter;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief Defines common import parameters
|
/** Defines common import parameters */
|
||||||
*/
|
|
||||||
struct ImportData
|
struct ImportData
|
||||||
{
|
{
|
||||||
ImportData()
|
ImportData()
|
||||||
|
@ -101,39 +100,52 @@ struct ImportData
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief Process standard arguments
|
/** Process standard arguments
|
||||||
*
|
*
|
||||||
* @param fill Filled by function
|
* @param fill Filled by function
|
||||||
* @param params Command line parameters to be processed
|
* @param params Command line parameters to be processed
|
||||||
* @param num NUmber of params
|
* @param num NUmber of params
|
||||||
* @return 0 for success
|
* @return 0 for success */
|
||||||
*/
|
int ProcessStandardArguments(ImportData& fill,
|
||||||
int ProcessStandardArguments(ImportData& fill, const char** params,
|
const char* const* params,
|
||||||
unsigned int num);
|
unsigned int num);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief Import a specific model file
|
/** Import a specific model file
|
||||||
* @param imp Import configuration to be used
|
* @param imp Import configuration to be used
|
||||||
* @param path Path to the file to be opened
|
* @param path Path to the file to be opened */
|
||||||
*/
|
const aiScene* ImportModel(
|
||||||
const aiScene* ImportModel(const ImportData& imp, const std::string& path);
|
const ImportData& imp,
|
||||||
|
const std::string& path);
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief assimp dump utility
|
/** assimp_dump utility
|
||||||
* @param params Command line parameters to 'assimp dumb'
|
* @param params Command line parameters to 'assimp dumb'
|
||||||
* @param Number of params
|
* @param Number of params
|
||||||
* @return 0 for success
|
* @return 0 for success*/
|
||||||
*/
|
int Assimp_Dump (
|
||||||
int Assimp_Dump (const char** params, unsigned int num);
|
const char* const* params,
|
||||||
|
unsigned int num);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief assimp extract utility
|
/** assimp_extract utility
|
||||||
* @param params Command line parameters to 'assimp extract'
|
* @param params Command line parameters to 'assimp extract'
|
||||||
* @param Number of params
|
* @param Number of params
|
||||||
* @return 0 for success
|
* @return 0 for success*/
|
||||||
*/
|
int Assimp_Extract (
|
||||||
int Assimp_Extract (const char** params, unsigned int num);
|
const char* const* params,
|
||||||
|
unsigned int num);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
/** assimp_cmpdump utility
|
||||||
|
* @param params Command line parameters to 'assimp cmpdump'
|
||||||
|
* @param Number of params
|
||||||
|
* @return 0 for success*/
|
||||||
|
int Assimp_CompareDump (
|
||||||
|
const char* const* params,
|
||||||
|
unsigned int num);
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
/** @brief assimp info utility
|
/** @brief assimp info utility
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,113 @@
|
||||||
|
/* Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
* obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
* this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
* execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
* Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
* do so, all subject to the following:
|
||||||
|
*
|
||||||
|
* The copyright notices in the Software and this entire statement, including
|
||||||
|
* the above license grant, this restriction and the following disclaimer,
|
||||||
|
* must be included in all copies of the Software, in whole or in part, and
|
||||||
|
* all derivative works of the Software, unless such copies or derivative
|
||||||
|
* works are solely in the form of machine-executable object code generated by
|
||||||
|
* a source language processor.
|
||||||
|
*
|
||||||
|
* 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE. */
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef HEADER_GENERIC_INSERTER_HPP_INCLUDED
|
||||||
|
#define HEADER_GENERIC_INSERTER_HPP_INCLUDED
|
||||||
|
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <new> // bad_alloc
|
||||||
|
|
||||||
|
|
||||||
|
template <typename char_type, typename traits_type, typename argument_type>
|
||||||
|
std::basic_ostream<char_type, traits_type>& generic_inserter(void (*print)(std::basic_ostream<char_type, traits_type>& os, argument_type const& arg), std::basic_ostream<char_type, traits_type>& os, argument_type const& arg)
|
||||||
|
{
|
||||||
|
using namespace ::std;
|
||||||
|
|
||||||
|
ios_base::iostate err = ios_base::goodbit;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
typename basic_ostream<char_type, traits_type>::sentry sentry(os);
|
||||||
|
if (sentry)
|
||||||
|
{
|
||||||
|
print(os, arg);
|
||||||
|
err = os.rdstate();
|
||||||
|
os.width(0); // Reset width in case the user didn't do it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (bad_alloc const&)
|
||||||
|
{
|
||||||
|
err |= ios_base::badbit; // bad_alloc is considered fatal
|
||||||
|
ios_base::iostate const exception_mask = os.exceptions();
|
||||||
|
|
||||||
|
// Two cases: 1.) badbit is not set; 2.) badbit is set
|
||||||
|
if (((exception_mask & ios_base::failbit) != 0) && // failbit shall throw
|
||||||
|
((exception_mask & ios_base::badbit) == 0)) // badbit shall not throw
|
||||||
|
{
|
||||||
|
// Do not throw unless failbit is set.
|
||||||
|
// If it is set throw ios_base::failure because we don't know what caused the failbit to be set.
|
||||||
|
os.setstate(err);
|
||||||
|
}
|
||||||
|
else if (exception_mask & ios_base::badbit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// This will set the badbit and throw ios_base::failure.
|
||||||
|
os.setstate(err);
|
||||||
|
}
|
||||||
|
catch (ios_base::failure const&)
|
||||||
|
{
|
||||||
|
// Do nothing since we want bad_alloc to be rethrown.
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
// else: no exception must get out!
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
err |= ios_base::failbit; // Any other exception is considered "only" as a failure.
|
||||||
|
ios_base::iostate const exception_mask = os.exceptions();
|
||||||
|
|
||||||
|
// badbit is considered more important
|
||||||
|
if (((exception_mask & ios_base::badbit) != 0) && // badbit shall throw
|
||||||
|
((err & ios_base::badbit) != 0)) // badbit is set
|
||||||
|
{
|
||||||
|
// Throw ios_base::failure because we don't know what caused the badbit to be set.
|
||||||
|
os.setstate(err);
|
||||||
|
}
|
||||||
|
else if ((exception_mask & ios_base::failbit) != 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// This will set the failbit and throw the exception ios_base::failure.
|
||||||
|
os.setstate(err);
|
||||||
|
}
|
||||||
|
catch (ios_base::failure const&)
|
||||||
|
{
|
||||||
|
// Do nothing since we want the original exception to be rethrown.
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
// else: no exception must get out!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed in the case that no exception has been thrown but the stream state has changed.
|
||||||
|
if (err)
|
||||||
|
os.setstate(err);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // HEADER_GENERIC_INSERTER_HPP_INCLUDED
|
Loading…
Reference in New Issue