Teil eines .obj Files (ASCII Text) schnell(!) auslesen... Ideen?



  • Hallo Hivemind ­čÖé

    ich muss die Definitionen fuer Dreiecke aus einem sehr sehr grossen obj File auslesen. Ein .obj, sieht so aus:

    # List of Vertices, with (x,y,z) coordinates
    v 0.123 0.234 0.345 1.0
    v ...

    ... allerlei anderes Zeug, bei mir typischer Weise ca. 5-7 Mio. Zeilen(!!!)

    # Face Definitions (see below)
    f 3/1 4/2 5/3
    f ...

    Ich brauche lediglich diese Informationen, also die Zeilen die mit "f" beginnen (keine Zeile davor kann laut Definition zwischendurch mit "f" beginnen).

    Die erste Zahl des Tupels "3/1" bspw. gibt mir den Index 'n' bzw. den Wert welchen ich dann benutze um in ein Array zu speichern:

    array[n] = 1;

    Soweit so klar, jedoch handelt es sich um wirklich sehr grosse Daten (Mesh eines Rangescans >120MB) und ich habe keine Idee wie man das intelligent sprich performant aus dem File rausbekommt.

    Ich kenne die Anzahl der Zeilen die mit 'v' beginnen, weiss aber nicht wie ich das ausnuetzen kann - ifstream, getline, boost?, ... soll mir alles recht sein, hauptsache es geht schnell ­čÖé

    Danke im Voraus fuer kreative Vorschlaege!



  • Hi,

    Hab keine Ahnung, aber vll. hilft dir weiter, Mal in Assimp reinzuschauen. Das ist ne Opensource-Bibliothek, die auch .obj l├Ądt: http://assimp.sourceforge.net/main_downloads.html



  • Danke fuer den Tipp, aber da ich mich wirklich nur dieser spezifischen Teil des Files interessiert wuerde ich gerne den Overhead vermeiden und das ganze selbst machen.



  • Das Projekt ist ja modularisiert und es gibt eine Datei "ObjLoader" oder so. Ich schlug nicht vor, die Bibliothek zu verwenden, sondern in den Quellcode zu schauen, um zu sehen, wie die das mit performantem Lesen machen.



  • schon klar, hab auch in den code reingeschaut... aber die lesen trotzdem alles ein und ich denke es muss schneller gehen ihre Loesung (siehe unten) mit switch case:

    while (m_DataIt != m_DataItEnd)
    	{
    		switch (*m_DataIt)
    		{
    		case 'v': // Parse a vertex texture coordinate
    			{
    				++m_DataIt;
    				if (*m_DataIt == ' ')
    				{
    					// Read in vertex definition
    					getVector3(m_pModel->m_Vertices);
    				}
    				else if (*m_DataIt == 't')
    				{
    					// Read in texture coordinate (2D)
    					++m_DataIt;
    					getVector2(m_pModel->m_TextureCoord);
    				}
    				else if (*m_DataIt == 'n')
    				{
    					// Read in normal vector definition
    					++m_DataIt;
    					getVector3( m_pModel->m_Normals );
    				}
    			}
    			break;
    
    		case 'f': // Parse a face
    			{
    

    Ich hatte da eher an offset Voodoo oder sowas gedacht ­čśâ trotzdem nochmal danke, ist trotzdem eine gute Anregung!



  • Brusik schrieb:

    ... soll mir alles recht sein, hauptsache es geht schnell ­čÖé

    Hallo Brusik,

    warum habt ihr es denn immer so eilig ..

    Folgender Vorschlag liest zumindest nichts ein, was nicht gebraucht wird. Ob das schnell genug ist - bitte selber beurteilen (Feedback erw├╝nscht).

    #include <iostream>
    #include <fstream>
    #include <limits>
    
    std::istream& ignore_but_f( std::istream& in )
    {
        for( char c; in >> c && c != 'f'; )
            in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
        return in;
    }
    
    int main()
    {
        using namespace std;
        ifstream datei("input.obj");
        while( datei >> ignore_but_f )
        {
            for( int idx, value; !is_endl( datei ) && datei >> idx >> Char<'/'> >> value; )
                ; // array[ idx ] = value;
        }
        return 0;
    }
    

    Das is_endl gibt's hier und das Char<> hier.

    Gru├č
    Werner



  • Werner, das ignore muss doch trotzdem alles einlesen um zu wissen, bis wohin es ignoreren kann/muss!
    Mir fiele jetzt aber auch keine bessere L├Âsung ein, ich denke, dass der Datei-Cache sowieso irgendwann alles eingelesen haben wird. Man kann ihn nat├╝rlich deaktivieren und irgendwie eine bin├Ąre Suche "auf der Festplatte" machen, allerdings denke ich, dass die Latenzen da dann wesentlich schlimmer kommen k├Ânnten, als eine 120mb-datei reinzustreamen und linear zu parsen.

    Edit: Ich w├╝rde lieber nen kleines Tool schreiben, dass mir ├╝ber Nacht von den Modellen im gr├Ąsslichen Ascii-Format bin├Ąre Versionen erstellt, und mir hinterher nie wieder Gedanken dar├╝ber machen ­čśë



  • Decimad schrieb:

    Werner, das ignore muss doch trotzdem alles einlesen um zu wissen, bis wohin es ignoreren kann/muss!

    ja sicher. Mit 'liest nicht ein' meinte ich: 'liest & speichert nicht in irgendwelchen Variablen'. Wenn nichts zus├Ątzlich gespeichert wird, muss daf├╝r auch kein Memory allokiert werden. Da sehe ich neben dem Zugriff auf das Filesystem den zweiten Flaschenhals.

    Es muss nat├╝rlich Zeichen f├╝r Zeichen der Datei gelesen werden, denn jedes weitere k├Ânnte ein LF oder 'f' sein.

    Decimad schrieb:

    Ich w├╝rde lieber nen kleines Tool schreiben, dass mir ├╝ber Nacht von den Modellen im gr├Ąsslichen Ascii-Format bin├Ąre Versionen erstellt, und mir hinterher nie wieder Gedanken dar├╝ber machen ­čśë

    Dann doch lieber gleich eine Datenbank. Aber ich bin gespannt, was Brusik sagt, wie lange die 5-7MB mit dem ignore_but_f dauern.

    Gru├č
    Werner



  • Ich werde das Ganze morgen genau so umsetzen und dann posten was passiert ist bzw. wenns noch eine weitere Idee gibt die Laufzeiten vergleichen ;o)

    Danke derweilen!



  • in der is_endl Funktion muss man vor die Zeile

    std::basic_istream< E, Traits >::sentry ok( in, true );
    

    noch ein

    typename
    

    stellen damit es compiliert... leider bricht der Code aber nach einem Item ab und ich hab keinen Anhaltspunkt wieso.

    Hab das Ganze jetzt mit getline straightforward implementiert, wuerde aber gerne Werners Idee testen sofern jemand eine Antwort auf das Problem hat.

    Cheers



  • Was hei├čt nach dem ersten Item, genau wo im Code geht der denn raus?



  • Brusik schrieb:

    in der is_endl Funktion muss man vor die Zeile

    std::basic_istream< E, Traits >::sentry ok( in, true );
    

    noch ein

    typename
    

    stellen damit es compiliert...

    Das stimmt; und in Zeile 18 muss vor das 'std::basic_istream< E, Traits >::int_type' auch ein typename. Ich habe das im Posting von is_endl korrigiert. Der MS-VC ist da v├Âllig gro├čz├╝gig ..

    Brusik schrieb:

    leider bricht der Code aber nach einem Item ab und ich hab keinen Anhaltspunkt wieso.

    Probier mal (nur f├╝r Debug-Zwecke) folgendes:

    while( datei >> ignore_but_f )
        {
            for( int idx, value; ; )
            {
                if( is_endl( datei ) ) break;
                if( !(datei >> idx) ) { cerr << "Err: idx" << endl; break; }
                if( !(datei >> Char<'/'>) ) { cerr << "Err: Char<'/'>" << endl; break; }
                if( !(datei >> value) ) { cerr << "Err: value" << endl; break; }
    
                array[ idx ] = value;
            }
        }
        if( !datei.eof() )
        {
            cerr << "Fehler beim Lesen" << endl; // <== hier Break Point setzen und in 'datei' reingucken, falls m├Âglich
        }
    

    Gru├č nach Wellington
    Werner

    @Edit: if( !datei.eof() ) hinzugef├╝gt



  • Danke f├╝r den Debug-Code Werner, der funktioniert genau wie erwartet v├Âllig ohne Probleme und gibt alle Daten wie gew├╝nscht aus!

    Der urspr├╝ngliche Code...

    for( int idx, value; !is_endl( inFile ) && inFile >> idx >> Char<'/'> >> value; )
    

    ... l├Ąsst sich zwar kompilieren, aber Eclipse vermerkt dazu:

    Multiple Markers at this Line
    -Syntax Error
    -Syntax Error
    

    und gibt nur die idx bzw. values der erste "f" Zeile zur├╝ck (@Eisflamme: er biegt nach der ersten Zeil ab). Sehr eigenartig...



  • Der kompiliert aber zeigt an "Syntaxfehler"?

    So geht's auch nich?

    int idx, value;
    for( ; !is_endl( inFile ) && inFile >> idx >> Char<'/'> >> value; )
    


  • Nein, an das hatte ich schon gedacht... das macht leider keinen Unterschied. Das Schr├Ąge ist ja wirklich der Umstand, dass der Code Compiliert aber nicht so arbeitet wie erwartet.

    Ich habe testweise den Code aneinander gereiht:

    int idx, value;
    		for( ; !is_endl( inFile ) && inFile >> idx >> Char<'/'> >> value; ) {
    			cout << value << " " << idx << endl;
    		}
    
    		for(/* int idx, value */; ; ) {
    

    und erhalte folgende Ausgabe:

    File opened
    ... Daten erste Zeile
    Err: idx
    Fehler beim Lesen



  • Brusik schrieb:

    Danke f├╝r den Debug-Code Werner, der funktioniert genau wie erwartet v├Âllig ohne Probleme und gibt alle Daten wie gew├╝nscht aus!

    Hei├čt das, dass obiger Code die obj-Datei ohne Fehler einliest ... und die urspr├╝ngliche Fassung zwar ├╝bersetzt, aber es nicht tut. Das w├╝rde ja bedeuten, dass der gcc falsch ├╝bersetzt ­čśĽ

    Brusik schrieb:

    Ich habe testweise den Code aneinander gereiht:

    int idx, value;
    		for( ; !is_endl( inFile ) && inFile >> idx >> Char<'/'> >> value; ) {
    			cout << value << " " << idx << endl;
    		}
    
    		for(/* int idx, value */; ; ) {
    

    und erhalte folgende Ausgabe:

    File opened
    ... Daten erste Zeile
    Err: idx
    Fehler beim Lesen

    Dies wiederum ist v├Âllig logisch, da die for-Schleife nur genau eine Zeile ohne das f├╝hrende 'f' liest. Stellst Du sie zweimal hintereinander, so trifft das zweite datei>>idx auf ein 'f' oder einen anderen Buchstaben, und liefert demzufolge einen Fehler.

    Generiere Dir doch einfach eine kleine obj-Datei mit einer Handvoll Zeilen zum ├ťben. Und versuche den Code zu debuggen. Hast Du im Debugger Zugriff auf den streambuf von 'datei'? Und kannst Du sehen wo die aktuelle Schreibposition ist?

    Falls dies in Deiner Umgebung nicht geht, so kannst Du folgenden streambuf

    class LC : public std::streambuf
    {
    public:
        typedef std::streambuf::int_type int_type;
        typedef std::streambuf::traits_type traits_type;
        explicit LC( std::streambuf* source ) 
            : m_line(0), m_column(0)
            , m_lf_occurs( true )
            , m_source( source )
            , m_c()
        {}
    
        friend std::ostream& operator<<( std::ostream& out, const LC& lc )
        {
            return out << "Line " << lc.m_line << " Column " << lc.m_column;
        }
    protected:
        virtual int_type underflow()
        {
            const int_type m = m_source->sbumpc();
            if( !traits_type::eq_int_type( m, traits_type::eof() ) )
            {
                if( m_lf_occurs )
                {
                    ++m_line;
                    m_column = 0;
                    m_lf_occurs = false;
                }
                m_c = traits_type::to_char_type( m );
                setg( &m_c, &m_c, &m_c + 1 );
                if( m_c == '\n' ) // LF
                    m_lf_occurs = true;
                ++m_column;
            }
            return m;
        }
    
    private:
        int m_line, m_column;
        bool m_lf_occurs;
        std::streambuf* m_source;
        char m_c;
    };
    

    .. benutzen, Der gibt Dir den aktuellen Stand Deines Lesezeigers aus, wenn Du ihn zwischen den file_buf und einen istream einh├Ąngst - geht etwa so:

    ifstream datei_( "input.obj" );
        LC lc( datei_.rdbuf() );
        istream datei( &lc );
    
        while( datei >> ignore_but_f )
        {
            for( int idx, value; !is_endl( datei ) && datei >> idx >> Char<'/'> >> value; )
                array[ idx ] = value;
        }
        if( !datei.eof() )
        {
            cerr << "Fehler beim Lesen an Position: " << lc << endl;
        }
    

    wie lange dauert denn so eine 5-7MB-Datei?

    Gru├č
    Werner



  • Werner Salomon schrieb:

    Hei├čt das, dass obiger Code die obj-Datei ohne Fehler einliest ... und die urspr├╝ngliche Fassung zwar ├╝bersetzt, aber es nicht tut.

    V├Âllig richtig, genau so sieht es aus... und zwar sowohl auf einer Linux als auch auf einer Mac Maschine (gcc 4.3.3 bzw. 4.2.1)!!!

    Aber es wird noch spannender ­čśë

    LC lc( inFile_.rdbuf() );
    	    istream inFile( &lc );
    
    	    while( inFile >> ignore_but_f )
    	    {
    	        for( int idx, value; !is_endl( inFile ) && inFile >> idx >> Char<'/'> >> value; ) {
    	        	cout << value << "-" << idx << endl;
    	        }
    
    	    }
    	    if( !inFile.eof() )
    	    {
    	        cerr << "Fehler beim Lesen an Position: " << lc << endl;
    	    }
    

    ... funktioniert ebenfalls ohne Murren und gibt alle idx bzw. values korrekt aus

    Werner Salomon schrieb:

    wie lange dauert denn so eine 5-7MB-Datei?

    Ich komm erst Mo. wieder an die Files, und es sind 100 - 140MB Files(!) nicht 5MB, sonst w├╝rde ich doch gar nicht einen Aufstand wegen Geschwindigkeit etc. machen ­čśë



  • Brusik schrieb:

    Werner Salomon schrieb:

    Hei├čt das, dass obiger Code die obj-Datei ohne Fehler einliest ... und die urspr├╝ngliche Fassung zwar ├╝bersetzt, aber es nicht tut.

    V├Âllig richtig, genau so sieht es aus... und zwar sowohl auf einer Linux als auch auf einer Mac Maschine (gcc 4.3.3 bzw. 4.2.1)!!!

    dann scheint es wohl ein Problem des Compilers zu sein .. unabh├Ąngig von der Plattform.

    Falls das mit std::istream geht, aber nicht mit std::ifstream, dann kannst Du in Deinem Fall die Variable inFile einfach casten .. ich mein' das so:

    ifstream inFile_( "input.obj" );
        istream& inFile = inFile_;
    

    Das sollte dann eigentlich funktionieren. Und man verliert auch keine Performance.

    Benutzt Du ├╝berhaupt std::ifstream oder ├Âffnest Du einen std::fstream?

    Gru├č
    Werner


Log in to reply