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


Anmelden zum Antworten