siredmar / mdcii-engine

Platform independent remake of the game Anno 1602/1602AD.
GNU General Public License v2.0
19 stars 3 forks source link

Fragen zur Insel.cpp #85

Open stwe opened 4 years ago

stwe commented 4 years ago

Ich habe mal drei Fragen zum Verständnis der Insel.cpp

https://github.com/siredmar/mdcii-engine/blob/master/source/mdcii/mdcii/src/insel.cpp

Dort gibt es zwei Member schicht1 und schicht2, welche jeweils mit der Breite und Höhe der Insel initialisiert werden. Warum zwei Schichten?

this->schicht1 = new inselfeld_t[this->width * this->height];
this->schicht2 = new inselfeld_t[this->width * this->height];

Später im Ctor wird die Funktion this->insel_rastern aufgerufen:

// void Insel::insel_rastern(inselfeld_t* a, uint32_t length, inselfeld_t* b, uint8_t width, uint8_t height)
this->insel_rastern((inselfeld_t*)inselhaus->data, inselhaus->length / 8, schicht2, this->width, this->height);

In dieser Methode sind mir zwei Sachen unklar. Der Zeiger a hat bereits die Inselhaus-Daten der Insel samt der jeweiligen x und y Position und der BebauungsId auf der Insel. Warum der Zeiger b?

Dann wird b in den Zeilen 61 - 69 mit den Daten aus a gefüllt. Die x und y Position werden jetzt aber in b so überschrieben, dass dort nicht mehr die Position auf der Insel steht, sondern die Werte den von buildingHeight und buildingWidth entsprechen. Warum wird das gemacht?

for (int y = 0; y < buildingHeight && feld.y_pos + y < height; y++)
{
    for (int x = 0; x < buildingWidth && feld.x_pos + x < width; x++)
    {
        b[(feld.y_pos + y) * width + feld.x_pos + x] = feld;
        b[(feld.y_pos + y) * width + feld.x_pos + x].x_pos = x;  // <---- warum 
        b[(feld.y_pos + y) * width + feld.x_pos + x].y_pos = y;  // <---- warum
    }
}

Danke.

siredmar commented 4 years ago

Hi! Grundsaetzlich ist dieser code noch alter Code, der zwar gerade noch zum rendern verwendet wird, aber noch entfernt wird. Die Idee dahinter ist aber ziemlich clever. Deine Fragen sind nicht in ein paar wenigen Saetzen erklaert, da gerade an dieser Stelle die Essenz des Renderers vorbereitet wird. Ich versuche es aber trotzdem.

Zu deiner Frage 1: Warum zwei schichten? Weil es im Savegame-Format sein kann, dass zwei Schichten abgespeichert werden. Jede Insel hat ein Flag, ob diese Modifiziert ist. Falls 'nein' findest du nur einen INSELHAUS chunk im savegame (schicht1). Falls 'ja', gibt es einmal den original INSELHAUS chunk der Insel (oder nur eine Referenz bei z.B. INSEL5) PLUS einen kleineren INSELHAUS chunk, der alle aktuellen Veraenderungen an den jeweiligen Stellen darstellt. Es kann folgende Faelle geben: 1 x INSELHAUS (original Insel) 2 x INSELHAUS (original Insel + diff layer) 1 x INSELHAUS (diff layer)

Zu wissen in welchem dieser drei Faelle man sich befindet haengt von dem jeweiligen INSEL5 chunk ab. Um das zu verstehen, musst man sich die .szs/.szm Files anschauen. Zu allem Ueberfluss werden im SZENE chunk noch leere Default Inseln (entweder per Filename oder per Zufall) an definierte Positionen platziert. Diese sind dann noch mit den entsprechenden INSELHAUS chunks beim savegame parsing zu versehen. Das zu verstehen war ein echter Brocken...

Ich hoffe damit klaeren sich einige Unklarheiten bzgl. der Pointern auf.

Frage 2: Warum werden die X- und Y-Positionen in b ueberschrieben? Das hat den Grund, dass das Darstellen effizient gestaltet werden kann. Wenn du dir die stadtfld.bsh mal ausgeben laesst, stellst du fest, dass es tausende kleine Bilder sind, die fast alle dieselbe Groesse haben. in haeuser.cod ist fuer jedes Haus die Groesse und Breite definiert. Die Teilbilder, aus denen sich dann fuer jeden Animationsschritt die Gebaeude zusammensetzen lassen folgen einer gewissen Indexcodierung. Im INSELHAUS chunk ist an einer Stelle an der ein Haus steht nur an einer Stelle (naemlich am Feld des unteren linken Ecks des Gebaeudes) die Gebaeude-ID gespeichert. Mit den Informationen

lassen sich also die Indizes der Teilbilder berechnen. Wandert man x,y ueber die Insel findet man also die entsprechenden Felder vor mit vorbereiteten X,Y Koordinaten des Teilgebaeudes. Das macht das Berechnen des Index fuer dieses Feld einfacher (https://github.com/siredmar/mdcii-engine/blob/master/source/mdcii/mdcii/src/insel.cpp#L208).

Fuer den Code, der bald eingebaut wird, schau dir mal worldbmp oder islandbmp fuer die Verwendung des neuen Codes an. Das Grundprinzip fuer deine Frage relevant wird dort aehnlich geloest.

Falls weitere Fragen sind, dann nur zu. Ich hoffe ich konnte zumindest einen Anstoss geben. Grundsaetzlich sind die Orginaldaten inkonsistent und schwer zu verstehen. Ich bin aber inzwischen an einem Punkt an denen ich inzwischen viele Raetsel loesen konnte.

stwe commented 4 years ago

Vielen Dank für Deine klärenden Worte - jetzt geht es mir besser :) Ich hatte mir nämlich den ganzen Sonntag überlegt, was der Auto des Originalcodes sagen möchte. Das hilft mir also auf jeden Fall weiter.

Was Deine Anmerkung zum ursprünglichen Code angeht, so bin ich bei Dir. Da hätte darüber hinaus auch der eine oder andere Kommentar geholfen. Ich habe grundsätzlich gute Erfahrungen damit gemacht, nicht nur Sourcecode zu teilen, sondern die Idee dahinter zu kommentieren - nur so finden sich letztlich Menschen, die mir bei der Verbesserung des Programms helfen.

siredmar commented 4 years ago

Hast du denn Interesse mitzumachen? Ich stehe da alleine da und habe gerade eine zeitliche Flaute :-) Ich bin noch dabei das nanogui-sdl Framework so weit hinzuhacken, dass es zumindest die Basics an Widgets hat, die ich brauchen werde. Falls das dann endlich abgeschlossen ist, wollte ich mich daran machen meinen existierenden Render-Code einzubauen und die alte Codebase dann entgueltig loszuwerden.

stwe commented 4 years ago

Bin gerade auf der Arbeit. Ich sage dir mal später was dazu.

Armin Schlegel notifications@github.com schrieb am Mo., 21. Sep. 2020, 09:21:

Hast du denn Interesse mitzumachen? Ich stehe da alleine da und habe gerade eine zeitliche Flaute :-) Ich bin noch dabei das nanogui-sdl Framework so weit hinzuhacken, dass es zumindest die Basics an Widgets hat, die ich brauchen werde. Falls das dann endlich abgeschlossen ist, wollte ich mich daran machen meinen existierenden Render-Code einzubauen und die alte Codebase dann entgueltig loszuwerden.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/siredmar/mdcii-engine/issues/85#issuecomment-695946808, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWT2DFOEARBJFIJVHW4Q5LSG35ITANCNFSM4RT3FK3A .

stwe commented 4 years ago

Ich bin eher der Hobby Engine Programmierer und habe in den letzten Monaten meistens an meiner SgOgl Engine gearbeitet. Programmiert ist das Ding in C++ und man kann (rudimentär) mit Lua eine Scene bauen. Die eigentliche Darstellung erfolgt mit OpenGL. Auch ansonsten mach ich eher Stuff für Programmierer zB in PHP. Jetzt bin ich an dem Punkt dass ich genau diese Engine nach Java portieren wollte und zum Teil auch schon habe. Drüber hinaus wollte ich meinen Fokus mal wieder in Richtung 2D lenken. Die Darstellung von 2D Spielewelten ist viel subtiler als ein 3D Terrain, wenn auch letzteres natürlich schöner ist. Es gibt kein vernünftiges Tutorial, wie man eine Isometrische Fläche mit Höhenunterschieden programmiert. Flach ist kein Problem. Anno1602 war für mich immer das beste Beispiel einer perfekt umgesetzten Isometrischen Grafik. Ich möchte verstehen, wie das funktioniert - es ist der bestimmt achte Anlauf. Am Ende würde ich das so umsetzen wollen, dass die IsoDarstellung in meine Engine kommt, quasi als Basis für Iso2D Spiele. Damit hätte ich was brauchbares geschaffen.

Wo soll denn die Reise mit Deinem Repository hingehen? Ich werde mir auf jeden Fall Deinen Code am Wochenende mal ansehen und in ein VC++ portieren. Als Paketmanager verwende ich Conan. Letztlich werde ich dann erstmal versuchen, Deinen Code zu verstehen.

Zu Deiner "Flaute": Deine Videos zeigen, dass du bisher sehr sehr gute Ergbnisse erzielt hat. Vielleicht solltest Du Dir überlegen, ob es unbedingt so sein soll, das ganze Spiel nachzubauen oder vielleicht doch eher weniger und dafür besser.

siredmar commented 3 years ago

Klingt interessant was du so bisher gemacht hast. Bei Anno1602 gibt es ja nur zwei Hoehen: Meeresspiegel und Insel. Berge sind nur groessere Texturen, deren Platz nicht bebaubar ist.

Mein Repo soll da hin gehen, dass ich zumindest als einheitliche Schnittstelle das Savegame (und damit auch das Inselformat) sowie die Spieldaten (Grafiken, Texte, Musik) benutze. Mehr anzuschliessen gibts ja auch nicht. Ich hab jetzt nicht vor jeden Bug nachzubauen. Meine kleine Roadmap sieht erstmal so aus:

Die groessere Roadmap danach:

Ob ich mir eigene Strukturen ueberlege oder zumindest als Schnittstelle die originalen, bleibt sich vermutlich egal. Diese Ganzen Mechanismen werden ohnehin benoetigt.

Derzeit habe ich keinen Wert auf portierbarkeit gelegt. Ich entwickle unter Linux und habe gar kein Windows bzw. die Moeglichkeit unter Windows zu bauen oder sogar zu testen. Es ist sicherlich nicht verkehrt, das mal unter Windows laufen zu sehen. Ich habe aktuell keinen Paketmanager fuer C++ LIbraries. Ich verwende aktuell nur eine Hand voll, und die sind mit eingecheckt bzw. als git subtree eingebunden. Gerade nanogui-sdl hat bisher einige Aenderungen erfahren.

Um den Code zu verstehen, schau dir wie gesagt am Besten mal worldbmp und islandbmp an. Da gibts zumindest mal viel der kompletten Kette zu sehen.

stwe commented 3 years ago

Ich habe mir unter der Woche mal Dein Projekt geschnappt und versucht, es mit dem VS C++ Compiler unter Win10 zum Laufen zu bringen. Mit den ganzen Abhängigkeiten war das nicht ganz so einfach, aber alle Beteiligten sind unverletzt geblieben und das Programm wird jetzt fehlerfrei übersetzt.

Ich weiß nicht, ob es meiner History Ed. von Anno 1602 oder/und Windows liegt, aber meine Pfade zu den Anno Files sind teilweise komplett in Großbuchstaben. Hier waren im Code zahlreiche Änderungen notwendig. Vielleicht wäre es daher hilfreich, die Strings zu den Files als Konstanten zu speichern und mit letzteren zu arbeiten. Gut sind die zahlreichen Error-Meldungen. Es ist kein Muss, aber ich gebe dann immer noch die Datei oder den Klassennamen in der Fehlermeldung an und kann Spdlog empfehlen.

Dann - wie gesagt - nutze ich die aktuelle 1602 Version (History Ed.). Wenn ich jetzt ein Spielstand lade, stimmt die Integrität der START.BSH nicht. Die Funktion ImageHasNoErrors prüft, ob x > width und gibt false zurück, wenn das der Fall ist. Wenn ich die Rückgabe temp. ignoriere folgen im weiteren Verlauf weitere Fehler mit der Rückgabe der Bilder.

Ist Dir bekannt, ob die History Ed. kompatibel mit dem BSH Reader ist? Auf jeden Fall kann ich mir jetzt mal den Code unter Win10 in Ruhe ansehen und debuggen.

siredmar commented 3 years ago

Gut zu wissen, dass es zumindest auch unter VS C++ kompiliert. Wegen den Pfadnamen: Ob gross oder klein, sollte egal sein. HabIch speichere mit boost::filesystem oder std::filesystem (kann mich gerade nicht erinnern, aber unerheblich) den directory tree des Spielverzeichnises und such dann nach den filenames (alles lowercase) und geb dann den Index im Directory tree fuer das korrekte File zurueck. Da ist dann wieder die Gross- und Kleinschreibung beruecksichtigt. Ich hab die neue Anno 1602 History Edition auch mal getestet. Die haben irgendetwas mit den BSH Files gemacht, sodass die direkt erstmal nicht kompatibel sind und der BSH Parser auch Fehler ausspuckt.

Ich unterstuetze aktuell 'Neue Inseln,neue Abenteuer' bzw. die Koenigsedition. Die urspruengliche allererste Version (ich nenn diese Vanilla) geht nicht, weil hier noch viele Texte in der exe Hardkodiert sind. In NINA bzw. KE wird da die Texte.cod verwendet. Diese lese ich aus und verwende die.

roybaer commented 3 years ago

Beim Übersetzen mit VS C++ ist Vorsicht geboten, weil das Programm Bitfelder auf Daten benutzt, die aus einer Datei kommen. Das Problem ist hier, dass der C++-Standard nicht definiert, wie der Compiler die Bitfelder im Speicher zu sortieren hat und es da auch tatsächlich Unterschiede gibt. Von daher wäre meine dringende Empfehlung, bei GCC bzw. MinGW zu bleiben, weil darauf die komplette Codebasis ausgelegt ist.

Ansonsten stehe ich bei Fragen zu meiner ursprünglichen Codebasis gerne zur Verfügung. Als Lesehilfe für die alte Codebases ist es nützlich zu wissen, dass sie im Grunde ein Textur-Dumper-basierter Insel-zu-Bitmap-Konverter mit drumherum gestricktem Welt-zu-Bitmap-Konverter und daran angestricktem SDL-Frontend ist. Auf diese Art war die Codebasis nämlich zwischen 2011 und 2015 als nicht-öffentliches Ein-Mann-Projekt eines deutschsprachigen Programmierers parallel zum Reverse-Engineering der Dateiformate organisch gewachsen.

stwe commented 3 years ago

@roybaer Danke danke. Ich komme gern darauf zurück. Bei mir ist es momentan so, dass ich mich erstmal mit den Dateiformaten beschäftige (bsh, cod, col, scp, bebauung.txt). Zu den Bitfeldern: Ich hatte mich zwangsläufig damit beschäftigen müssen und ebenfalls gelesen, dass die C++ Compiler hier nicht einheitlich arbeiten. Hinzu kommt, dass keine Vergleichswerte aus den Dateien existieren, mit denen ich oder andere die eigenen FileReader testen könnten. Dann könnte man unter C++ auch sauber auf die unterschiedlichen Compiler reagieren. Nehmen wir aber mal Java. Dort habe ich das Problem Bits erstmal so gelöst (keine Ahnung, ob das Richtig ist): Beim Einlesen z.B. der big.scp erhalte ich einen INSEL5 und einen INSELHAUS Chunk. Den INSELHAUS Chunk wiederum lese ich ein, indem ich der Reihe nach 8 Byte folgender Struktur lese:

    typedef struct
    {
        uint16_t bebauung;
        uint8_t x_pos;
        uint8_t y_pos;

        uint32_t rot : 2;
        uint32_t ani : 4;
        uint32_t unbekannt : 8; // Werte zwischen 0 und ca. 17, pro Insel konstant, mehrere Inseln können den gleichen Wert haben
        uint32_t status : 3; // 7: frei, 0: von spieler besiedelt, 1: von spieler erobert (?)
        uint32_t zufall : 5;
        uint32_t spieler : 3;
        uint32_t leer : 7;
    } inselfeld_t;

Daraus habe ich gemacht:

    // original 8 bytes
    public int devId;
    public int xPosOnIsland;
    public int yPosOnIsland;

    public int rotation;
    public int animationCount;
    public int unknow;
    public int state;
    public int random;
    public int player;
    public int empty;

Bei der big.scp kommt das erste Inselfeld mit der Id = 1201, PosX = 0, PosY = 0 und der Rest entspricht den Hexwerten C7 C3 DF 01 bzw. in Dez = 31441863. Dann habe ich mir die 32 Bits von dem Int angesehen:

                         7 |       15 |      7|            15 |       1 |  3  ->> Wert
    0000000 | 111 | 01111 | 111 | 00001111 | 0001 | 11
               7 |      3|          5 |     3 |              8 |       4 |    2  ->> Anzahl der Bits

In der oberen Reihe stehen die Werte, die ich mit dem C++ Programm erhalte und von den kein Mensch weiß, ob die richtig sind. Hier ist der Text etwas schlecht formatiert. Unten steht die Anzahl der Bits.

Gut, wie lese ich das jetzt aus:

    /**
     * Extract k bits from position p.
     *
     * @param number The given value.
     * @param k Number of bits to extract.
     * @param p The position from which to extract.
     * @return The extracted value as integer.
     */
    public static int bitExtracted(int number, int k, int p) {
        return (((1 << k) - 1) & (number >> (p - 1)));
    }

Damit erhalte ich unter Java die gleichen Werte, wie unter C++ und bin das struct mit den Bits los.

Wie gesagt: alles hoch experimentell, aber eine hübsche Spielwiese. Am Wochenende will ich mal versuchen, ob ich mit den Daten was in Richtung Rendering anfangen kann.

roybaer commented 3 years ago

Na ja. Das dedizierte Sprachfeature ist schon deutlich eleganter. Es ist nur schade, dass der Standard da nicht eine bestimmte Variante vorschreibt oder zwischen verschiedenen sinnvollen Varianten wählen lässt. Das expandierte Struct für das Inselfeld braucht natürlich auch deutlich mehr Speicher.

Zu den Designentscheidungen bei meinem Code gehörte auch der Versuch, nicht zu weit über die Hardwareanforderungen der Originalengine hinaus zu gehen. Die lief auf einem 100MHz Pentium mit 16MB RAM.

siredmar commented 3 years ago

Na ja. Das dedizierte Sprachfeature ist schon deutlich eleganter. Es ist nur schade, dass der Standard da nicht eine bestimmte Variante vorschreibt oder zwischen verschiedenen sinnvollen Varianten wählen lässt. Das expandierte Struct für das Inselfeld braucht natürlich auch deutlich mehr Speicher.

Zu den Designentscheidungen bei meinem Code gehörte auch der Versuch, nicht zu weit über die Hardwareanforderungen der Originalengine hinaus zu gehen. Die lief auf einem 100MHz Pentium mit 16MB RAM.

Sowas kann ma später immernoch einbauen. Das hat für mich aktuell Prio null.

stwe commented 3 years ago

@siredmar Ich habe einfach weiter die neueste Edition von Anno genommen und stumpf weiter die Daten ausgelesen und genutzt. Damit konnte ich die "lit.scp" und "big.scp" rendern. Auffallend ist, dass es immer im oberen Bereich der Insel zu Darstellungsfehlern kommt. Ob das jetzt an den BSH Dateien liegt oder an mir oder nur die Rotation falsch berechnet wurde, kann ich nicht sagen. Grundsätzlich kann ich aber - Stand heute - keine weiteren Fehler mit der History Ed. feststellen. Umgesetzt habe ich das mit Java; das Renderung erfolgt mit meiner eigenen RenderEngine und OpenGL. Mein Rechner verfügt über eine Grafikkarte und zwei 16GB Ram-Riegel, welche ich schamlos nutze. Die Performance kann so später mit Instancing noch erheblich gesteigert werden. Außerdem kann mein Prototyp in Java natürlich einfach nach C++ portiert werden - wenn man das möchte.

Jetzt eine Frage an alle. Ich habe ein Bild zugefügt. Wie man sieht, sind die Berge nicht korrekt dargestellt. Ich habe jedoch gesehen, dass es "halbe Bilder" gibt, die ich offenbar für eine fehlerfreie Darstellung benötige. Kann mir jemand ganz abstrakt erklären, unter welchen Bedingungen ich ein halbes Bild statt einem ganzen Bild rendern muss? Gibt es noch andere "Sorten" von Bildern? Oder bin ich hier auf dem Holzweg?

IsoH

roybaer commented 3 years ago

Kann mir jemand ganz abstrakt erklären, unter welchen Bedingungen ich ein halbes Bild statt einem ganzen Bild rendern muss? Gibt es noch andere "Sorten" von Bildern? Oder bin ich hier auf dem Holzweg?

Hast du dir die Dokumentation zur Grafikreihenfolge angesehen? Die „halben Bilder“ werden für alles gebraucht, was nicht die vordere Ecke oder gänzlich unsichtbar ist. Ein Objekt mit 2x2 Feldern braucht also vier Grafiken, von denen die hintere unsichtbar ist und die beiden seitlichen nur jeweils die Hälfte enthalten, die nicht schon von der vorderen Ecke erfasst wird. Die müssen dann noch in der richtigen Reihenfolge gezeichnet werden, was aber bei OpenGL dank Z-Puffer trivial sein dürfte.

Hast du bei den Inseldateien mal im Hex-Editor nachgesehen, ob die noch auffällige zusätzliche Daten enthalten, die ggf. fälschlicherweise als Inselfelder interpretiert werden? Wenn nicht, dann ist der Fehler wahrscheinlich in deinem Code.

stwe commented 3 years ago

Kann es sein, dass sich die Einträge in der grafiken.txt erledigt haben und zwingend die Werte aus der haeuser.cod genommen werden müssen?

Wenn ich zB die lit.scp rendere (siehe oben), dann fällt auf, dass ich bei dem Code von @siredmar an Position (0, 0) die Berechnungen mit dem GFX 758 für die ID 1201 starte. Wenn ich mir die grafiken.txt ansehe, dann starte ich mit dem GFX 752. Die Differenz von sechs zieht sich dann wie ein roter Faden durch die Werte. Wenn ich die GFX Ids von @siredmar in meinen Code einsetze, werden auch die Berge richtig gezeichnet.

In der bebauung.txt ist die ID 1201 für das Meer mit 12 Animationsstufen angegeben. In der haeuser.cod steht dort die Zahl 6.

    @Nummer:    +1
    HAUSFERT =  Nummer
    Id:         IDMEER+0
    Gfx:        GFXMEER+78
    Baugfx:     BGFXMEER+6
    Kind:       MEER
    KreuzBase:  BASE
    Posoffs:    HIGHMEER
    Highflg:    0
    Size:       1, 1
    Rotate:     0
    PlaceFlg:   1
    AnimAdd:    1
    AnimAnz:    6
    AnimTime:   130
    Objekt:     HAUS_PRODTYP    
      Kind:       ROHSTOFF
      Ware:       FISCHE
siredmar commented 3 years ago

Ich hab gerade den Code nicht vor mir, wundere mich aber wo du diese txt Dateien überhaupt in diesem repo gefunden hast. Den Cod Parser, habe ich vor >1 Jahr gebaut und ersetzt seither die Verwendung dieser Dateien (haeuser.txt und bebauung.txt) auf einem generischen Weg.

Du solltest also die Id's aus der cod Datei benutzen. Zumal es Unterschiede in den id's zwischen der vanilla und NINA gab. Nicht auszumalen, wie sich die im Rahmen der neuen History Edition geändert haben könnten.

stwe commented 3 years ago

Ok, ich habe den Fehler gefunden. An einer Stelle hatte ich eine Referenz statt einer Kopie zurückgegeben und somit im weiteren Verlauf immer dasselbe Objekt geändert. Böse Falle, die mich einiges an Zeit gekostet hat. Meine Frage, mit der ich dieses Ticket geöffnet habe, hat sich damit auch geklärt - ich habe es jetzt kapiert, nachdem ich den Code fast auswendig kenne. Ich kann die lit.scp rendern....Der Code ist beir mir auf Github. Die Cod Files nutze ich indirekt, indem ich mir unter C++ den fertigen JSON String in eine Datei umleite und unter Java eben diese wieder einlese. Damit konnten die Grafiken.txt und Bebauung.txt über Bord geworfen werden.

Render

siredmar commented 3 years ago

@stwe Super. Beachte aber bitte, dass diese haeuser.cod nicht einheitlich ueber alle Varianten von Anno ist. Du kannst auch cod_parser in meinem Repo verwenden um dir das JSON ausgeben zu lassen.

Ist dein Code public?

stwe commented 3 years ago

Der Code ist öffentlich: https://github.com/stwe/Islands1648

Die JSON Daten habe ich mir von Deinem Parser ausgeben lassen.

siredmar commented 3 years ago

@stwe ich hab mir deine Insel5 Definition angesehen. Zu deinem eigenen Wohl, nimm bitte die Chunkdefinitionen von hier https://github.com/siredmar/mdcii-engine/tree/master/source/mdcii/mdcii/include/mdcii/gam

Diese basieren alle auf den original Anno Strukturdefinitionen (mehr Code gabs leider nicht). Diese hab ich einst von Sir Henry bekommen, die diese schon vor vielen Jahren von W. Reiter bekommen hat.

stwe commented 3 years ago

danke für die Hilfe, die neuen Definitionen habe ich umgesetzt und darüber hinaus festgestellt, dass es grundsätzlich möglich ist, mit der neuen Version ein Savegame zu rendern. Letzteres bereitet mir allerdings Kopfzerbrechen und für Nachwelt möchte ich meine ursprüngliche und nicht funktionierende Idee hier mal vorstellen:

(1) Jedes Tile ist ein Mesh (die Daten sind dabei für alle immer gleich)

        float[] vertices = new float[] {
                // pos      // tex
                0.0f, 0.0f, 0.0f, 0.0f,
                1.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 1.0f, 0.0f, 1.0f,

                0.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 1.0f, 0.0f,
                1.0f, 1.0f, 1.0f, 1.0f
        };

Dafür wird ein Buffer erstellt und auf die GPU kopiert.

(2) Jedes Tile hat eine eigene Position und eine eigene Größe (Transformationsmatrix). Dafür wird ebenfalls ein Buffer erstellt und auf die GPU kopiert. Nun kann ich ich alle Tiles mit nur einem einzigen Renderbefehl darstellen (Instancing). Die Welt wird dabei quasi nur "eingeblendet".

(3) Denkfehler entdeckt. Die Texturen müssen noch auf die Tiles. Das Problem hierbei ist die schlichte Masse der Texturen. In der Stadtfld.bsh sind es schon allein fast 6000. Unter OpenGL kann ich 16 Stück gleichzeitig binden. Ohne zu weit in die Tiefe zu gehen....Ich wollte das Problem mit der "Bindless Texture" Extension umgehen. Die Idee war: ich lasse jeder Textur ein Handle zuweisen und mache dieses Handle der GPU bekannt. Jetzt habe ich einen weiteren Buffer auf die GPU kopiert, welcher für jedes Tile eine Information zu dem Handle enthält (ein Integer). Damit konnte ich weiter mit der Instancing Technik rendern. Es ist wirklich extrem schnell.

Ende vom Lied: mehr als ca 900 bis 1000 bindless Textures sind gleichzeitig nicht möglich. Irgendwo musste ja die Grenze sein, aber das ist schon echt schwach.

Wieder was gelernt. Und jetzt muss ich erstmal das Rendering überarbeiten.

stwe commented 3 years ago

Das Rendering habe ich nun erstmal mit sog. Texture Arrays gelöst. Für den Code verweise ich auf mein hier genanntes öffentliches Repository.

siredmar commented 3 years ago

@all: Falls sich jemand berufen fuehlt die Rendering engine von mdcii auf Vordermann zu bringen, waere ich nicht ungluecklich. Ich habe keine Ahnung von OpenGL.

Green-Sky commented 3 years ago

@siredmar Ich hab mal die aktuelle Version gepullt und kämpf grad noch mit compilier Fehlern... heißt: ich werde erst ein mal Probleme beheben :smirk:

siredmar commented 3 years ago

Kannst du kurz teilen, um was fuer Compiler Fehler es hierbei geht? Wundert mich, da es eigentlich bauen sollte.

Green-Sky commented 3 years ago

Klar,

stwe commented 3 years ago

installiere Dir mal alle Abhängigkeiten, wie in dem Dockerfile beschrieben: https://github.com/siredmar/mdcii-engine/blob/master/docker/Dockerfile

Das lief bei mir unter 18.04

Green-Sky commented 3 years ago

@stwe der einzige unterschied scheit gcc8 zu sein. dass es mit gcc9 nicht mehr compilier (also die tests, der rest geht) ist aber trozdem ein problem. Ich probier jetzt erst noch clang..

stwe commented 3 years ago

Hau Dir einfach mit Virtual Box das alte Ubuntu neu drauf und anschließend die Abhängigkeiten wie beschrieben. Das geht ganz fix. Clang habe ich nicht probiert.

Green-Sky commented 3 years ago

mit clang 6,7,8 und 9 bekomm ich folgenden fehler:

an mehreren stellen im code...

Green-Sky commented 3 years ago

@stwe tatsache mit gcc8 baut alles. Ich werde mir jetzt aber erstmal die clang fehler anschauen. Ich persönlich versuche immer code zu schreiben, der überall compilert.

siredmar commented 3 years ago

Danke @Green-Sky Falls das Code ist, bei dem die Dateinamen auf deutsch ist, dann ist das noch Code der ohnehin so schnell wie moeglich sterben sollte. Meine Files sind alle auf englisch benannt und der komplette Inhalt ist Englisch. Waere cool, wenn du kurz sagen koenntest, wo einige dieser Fehler auftreten.

Green-Sky commented 3 years ago

@siredmar schlechte nachrichten: die sind alle in nanogui-sdl

siredmar commented 3 years ago

@Green-Sky ja diese Library ist einfach mies, es gibt nur leider keine bessere Alternative fuer SDL2 dazu. Da steht wohl irgendwann noch ein groesseres Refactoring an.

Green-Sky commented 3 years ago

compiler output paste example:

/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:20: error: type 'float' cannot be narrowed to 'Uint8' (aka 'unsigned char') in initializer list [-Wc++11-narrowing]
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                   ^~~~~~~~~
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:20: note: insert an explicit cast to silence this issue
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                   ^~~~~~~~~
                   static_cast<Uint8>( )
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:31: error: type 'float' cannot be narrowed to 'Uint8' (aka 'unsigned char') in initializer list [-Wc++11-narrowing]
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                              ^~~~~~~~~
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:31: note: insert an explicit cast to silence this issue
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                              ^~~~~~~~~
                              static_cast<Uint8>( )
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:42: error: type 'float' cannot be narrowed to 'Uint8' (aka 'unsigned char') in initializer list [-Wc++11-narrowing]
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                                         ^~~~~~~~~
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:42: note: insert an explicit cast to silence this issue
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                                         ^~~~~~~~~
                                         static_cast<Uint8>( )
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:53: error: type 'float' cannot be narrowed to 'Uint8' (aka 'unsigned char') in initializer list [-Wc++11-narrowing]
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                                                    ^~~~~~~~~
/home/green/workspace/github/mdcii-engine/source/thirdparty/nanogui-sdl/nanogui-sdl/sdlgui/common.cpp:90:53: note: insert an explicit cast to silence this issue
  SDL_Color color{ r() * 255, g() * 255, b() * 255, a() * 255 };
                                                    ^~~~~~~~~
                                                    static_cast<Uint8>( )
Green-Sky commented 3 years ago

@siredmar ich hab schon einmal eine gui in <200 zeilen selber geschrieben. ich glaub nicht, dass wir nanogui wirklich brauchen, zumindest in der form wie es gerade ist.

siredmar commented 3 years ago

@Green-Sky hab googletest geupdated. Habe es gerade tesetweise auf einem ubuntu 20.04 mit g++ 9.3.0 gebaut.

Green-Sky commented 3 years ago

@siredmar sieht gut aus, mit gcc9 kommen jetzt auch die fehler wie bei clang, für die ich gerade einen fix anfertige

siredmar commented 3 years ago

@Green-Sky es gibt uebrigens noch n known issue fuer Release builds. Da schmiert das Programm im Menue ab. Hab das noch nicht verfolgt an was das liegt. Wenn du es als Debug baust, dann sollte es aber laufen.

./mdcii-sdltestd -p <path/to/anno_nina>

Green-Sky commented 3 years ago

@siredmar gut zu wissen, ich bau bereits debug. Ich halte das übrigens für keine gute idee, dass wir aktuell auf release defaulten...

Green-Sky commented 3 years ago

@siredmar pls merge

OK ich hab mal tests gemacht.

Green-Sky commented 3 years ago

Da schmiert das Programm im Menue ab. Hab das noch nicht verfolgt an was das liegt.

Eine schnelle Analyse sagt mir, dass ein ReferenceCounter negativ wird. Grobe Vermutung ist, dass der Compiler da was wegoptimiert. Und in den meisten Fällen ist dass des Resultat von UndefinedBehaviour...

siredmar commented 3 years ago

Ich muss meine Aussage, dass ich auf Ubuntu 20.04 mit g++ 9.3.0 gebaut habe, revidieren. googletest hat aktuell nach wie vor den Issue (finde den Issue gerade auf die Schnelle nicht). Man muss mdcii so bauen:

$ mkdir -p build
$ cd build
$ cmake -DCMAKE_CXX_FLAGS="-Wno-error=deprecated-copy" -DCMAKE_BUILD_TYPE=Debug
$ make

Werde das bei Gelegenheit noch in der Readme ergaenzen

siredmar commented 3 years ago

@siredmar pls merge

OK ich hab mal tests gemacht.

* Königs edition; funktioniert

* gog englisch; segfault in protobuf (text_cod.cpp:112) #90

* englische cd version?; segfault in protobuf (text_cod.cpp:112) #90

@Green-Sky die Segfaults mit den englischen Versionen sollte (hoffentlich) mit #92 der Vergangenheit angehoeren. #90 konnte ich dafuer leider nicht benutzen (mehr Infos dazu siehe #92) Danke fuer die PRs :+1:

Green-Sky commented 3 years ago

@siredmar image

sieht gut aus! edit: also die englische gog version geht jetzt.

stwe commented 3 years ago

Hi Armin,

zwei Fragen; vielleicht kannst Du Dich dunkel erinnern.

Du hattest folgendes herausgefunden:

Es kann folgende Faelle geben: 1 x INSELHAUS (original Insel) 2 x INSELHAUS (original Insel + diff layer) 1 x INSELHAUS (diff layer)

Zu wissen in welchem dieser drei Faelle man sich befindet haengt von dem jeweiligen INSEL5 chunk ab.

Dem folgt folgender (verkürzt dargestellter) Code:

    // island is unmodified, load bottom islandHouse from island file
    if (island.modifiedFlag == IslandModified::False && islandHouse.size() <= 1)
    {
        // ganz viele tolle Sachen machen ...
    }
    else
    {
        // the island is modified, first chunk is bottom
        finalIslandHouse.push_back(islandHouse[0]);
        // a possible second chunk is top
        if (islandHouse.size() == 2)
        {
            finalIslandHouse.push_back(islandHouse[1]);
        }
        else
        {
             // auch hier werden wieder tolle Sachen ausgeführt
        }
    }
    bottomLayer = finalIslandHouse[0];
    topLayer = finalIslandHouse[1];

Nun habe ich mal wieder mein käuflich erworbenes Anno mit einem Endlosspiel gestartet und gleich danach davon ein Savegame erstellt. Oben siehst Du nun den Code, der immer ausgeführt wird, wenn ich das Savegame mit Deiner Engine lade. Das heisst, alle Layer werden (Überraschung) von dem "else" Block erzeugt. In allen INSEL5 Chunks ist das modified Flag auf false gesetzt, was ja Sinn macht. Jeder INSEL5 Block hat zwei INSELHAUS Chunks, wobei letzterer davon keine Daten beinhaltet. Meistens folgt noch ein leerer HIRSCH2 Chunk. Eigentlich hätte ich gedacht, dass die Inseln gerade am Anfang direkt aus den SCP Files erstellt werden und der zweite Layer überhaupt nur dann vorhanden ist, wenn eine Bebauung erfolgt ist. Aber gut....

Meine Frage wäre nun, in welchem Fall tatsächlich die If Abfrage:

    if (island.modifiedFlag == IslandModified::False && islandHouse.size() <= 1)

erfüllt ist. Also ich habe da momentan keinen Fall für. Du wirst Dir aber was dabei gedacht haben...

Zweites Thema ist offtopic. Welche Entwicklungsumgebung für C++ nutzt Du unter Linux? Hast Du mal versucht, unter CLion einen Breakpoint zu setzen und die Anwendung dann schrittweise durchzugehen? Irgendwas stimmt da mit den Cmake files nicht; bei mir erscheint immer folgendes:

debug

roybaer commented 3 years ago

Eigentlich hätte ich gedacht, dass die Inseln gerade am Anfang direkt aus den SCP Files erstellt werden und der zweite Layer überhaupt nur dann vorhanden ist, wenn eine Bebauung erfolgt ist. Aber gut....

Das kann ich vielleicht aufklären:

Auf die Insel aus der SCP-Datei (aus dem Ordner NORD oder SUED) wird ggf. zunächst eine willkürliche passende Bewaldung aus einer weiteren SCP-Datei (aus NORDNAT oder SUEDNAT) geklatscht. Außerdem wird der aktuelle Animationsschritt vom Meer um die Insel und den Flüssen auf der Insel im Spielstand mitgespeichert, die entsprechenden Inselfelder gelten also zumeist als „geändert“.

Zu Armins Codebasis kann ich nicht viel sagen, weil die in dem Bereich mit meiner nicht mehr viel gemein hat.

Welche Entwicklungsumgebung für C++ nutzt Du unter Linux?

Als Entwicklungsumgebung unter Linux benutze ich KDevelop. Zumindest mit meinem Code hatten da auch Breakpoints funktioniert. Es kann allerdings sein, dass ich das Debug-Target dafür manuell eingerichtet hatte.

stwe commented 3 years ago

@siredmar @roybaer

Das ist ja hier so eine Art DevLog geworden. Also ich bin über die Frage, was aus welchem Layer benutzt wird, erstmal drüber hinweg gegangen. Momentan nehme ich einfach den Bottom Layer als Fallback und ansonsten den Top Layer.

Ich habe heute zum ersten mal einen Renderer fertiggestellt, von dem ich sagen würde, dass es derjenige ist, auf dem ich aufbauen kann. Also dass er funktioniert, so wie ich mir das vorstelle. Meine Idee war es, das komplette Rendering durch die GPU mit dem sog. Instancing erledigen zu lassen, und zwar so, dass auch Animationen möglich sind. Das mag zwar für ein 2D Spiel der Overkill sein, aber Projekte die auf eine andere Technik setzen gibt es bereits und mich interessiert einfach die technische Umsetzung.

Die Masse der Texturen (BSH Bilder in den stadtfld.bsh) von fast 6000 pro Zoomstufe waren das Problem. Sobald eine BSH Datei eingelesen war, lagen die Grafikdaten für die CPU seitige Verwendung vor, das half er GPU herzlich wenig. Die weiß davon nämlich nichts. Mit OpenGL können Texturen gebunden werden, aber in wesentlich geringerer Menge. Um eine angemessene Anzahl an Texturen zu erreichen, habe ich aus den BSH Bildern jeder Zoomstufe sog. TileAtlas Texturen erstellt. Für den besten Fall in der SGFX Variante ergeben sich jetzt nur noch zwei Bilder. Pro Bild werden in diesem Beispiel 64x64 = 4096 Bilder gespeichert. Grob gesagt, muss ich der GPU jetzt nur noch für jedes Tile mitteilen, an welchen Koordinaten die Textur liegt. Das kann man ausrechnen und speichern und später auch für eine Animation ändern.

Für jede der 17 Inseln schieße ich jetzt einen einzelnen draw-call ab. Ein weiterer draw-call geht für den umliegenden Tiefenwasser-Bereich drauf. Unter Windows kann man messen, dass das Rendering nach dem Senden der Daten an die GPU nahezu in nicht erwähnenswerter Zeit geschieht. Bei mir mit einer FPS von 1600. Da ist sogar noch Luft nach oben, da ich nicht sichtbare Inseln ebenfalls rendern lasse. Da wird noch geändert (Stichwort AABB). Für die Animation der Bebauung muss ich der GPU nur die jeweils neue Position der Textur mitteilen, was mit dem Renderer möglich ist und im Tiefenwasserbereich bereits implementiert ist. Technische Einzelheiten lasse ich hier mal aus. Wen es interessiert: ich ändere den VBO mit den Offsets, und zwar nur an den Stellen, an denen es notwendig ist.

Repo: https://github.com/stwe/Benno4j

siredmar commented 3 years ago

@stwe ziemlich schnieke! Ließe sich das auch nach golang portieren? Ich bin "gerade" dabei meine codebasis auf golang umzustellen, weil ich in Zukunft damit weiter machen möchte.

stwe commented 3 years ago

@siredmar Das ließe sich natürlich portieren, wobei ich golang bisher gar nicht auf dem Schirm hatte. Wenn ich mir dann aber https://github.com/g3n/engine angucke, dann hat die Sprache durchaus Potenzial. Ich werde mit den Animation der Inselbebauung weitermachen und dann habe ich noch das Problem mit dem Mauspicker zu lösen (Tile-Auswahl per Click). Letzteres ist dann doch schwieriger als gedacht. Dann hat man aber einen Prototyp, auf dem sich aufbauen lässt.