Rail Road Tycoon MFC in Action



  • @Finnegan sagte in Rail Road Tycoon MFC in Action:

    way E

    Hey Finnegan,

    Das Projekt ist auch auf GitHub, https://github.com/KahnSoft/FlexxRail

    Hab da nicht ganz die Muße, mir den Code anzusehen
    Ja ist auch nur ein Fetzen, zeigt aber den Vorgang der Chunk -Generierung ohne Cracks untereinander, da spielt ein gewisses Micro Overlapping eine rolle, das am ende aber kein Brot frisst (+2 auf den sizes der Chunks) Es wird sonnst Abstrus Rechen lastig wenn man da keine Tricks anwendet, also alles ist immer ein Kompromiss zwischen best of und Perform. Wenn man da Hunderte Loks mit Anhängern anzeigt, braucht man jeden Rechenquant, solange kein Gameplay statt findet, muss ich immer 1Khz Framerate erreichen, sonnst ist es später vorbei, also minimalste Operationen mit bestmöglichen Ergebnis.

    die Berglandschaft in dem Video ist nicht unbedingt eine Heightmap-Geometrie sondern ein eventuell generischeres 3D-Modell
    Genau es ist keine Highmap es ist alles dynamisch mit der Perlin Noise erzeugtes Grid- dessen Höhenwerte vom Shader mit Texpixeln im shader Texturmischer belegt werden, ich erzeuge damit riesige Karten mit Kantenlängen von bis zu 1 Million Dreiecke Seitenlänge was riesig ist, die Loks fahren mit rund 145Kmh alles bereist authentisch proportional, und ich kann die Kamera auf eine Fahrende Lok aufschalten und diese verfolgen, die benötigt in diesem Affentempo rund 5 Minuten um eine Seitenlänge zu passieren. Eine Highmap dieser Größe sprengt den Speicherrahmen , es ist auch nicht gerade günstig eine Textur zu haben die 10000 Pixel Breit und Hoch ist. Man nennt das mit vielen Highmaps dann Content-Streaming DirectX SDK macht das vor, aber ich muss wie gesagt insgesamt die Performance erhalten , es können später tausende Objekte mit den Chunks und dem LodSystem so veraltet werden. Da bin ich auch froh drüber es ohne ContentStreaming und Ohne HighMaps gemacht zu haben, zumal nach der Highmap dann nochmal selbige ColorMap als Textur benötigt wird, alles wird also Dynamisch zur Laufzeit erzeugt, die Qualität ist natürlich nicht die einer ColorMap, aber sie ist apokalyptisch groß, es wird also keine 3D -Lok Simulation wo jedes Gauge im Kesselhaus zu sehen ist, das zerbricht die Welt der unendlichen Weiten , das wird der große Vorteil gegenüber statischer Körperwelten. Also Aktienhandel Resourcenabbau alles Basiert auf Primitives, in einer erschlagenden Dimension.

    Nur die Ränder der "Szene",
    Genau keine Cracks in der Szene (NoGo) lediglich die Ränder haben eine Treppensignatur, aber die könnte ich wegrechnen ist aber unnötig out of SkyBox

    Parametrischen Kurven (Splines) meist eine Variable "Geschwindigkeit" haben.
    Da die Lok die Spline kennt (also die errechnet die selbst , es existieren real nur Kontrollpunkte) erzeugt sie für die Kurvigkeit eine Speed-Drossel die wirkt für Aufstieg bremsend für Abstieg Beschleunigend und für rechts links Radien Bremsend.

    void CSplineObjFollow::CalcSpeed()
    {
      const int dirSz = (int)m_DirN.size();
      if (m_SplineIdx < 0 || m_SplineIdx >= dirSz)
      {
        m_ConsecUphill = 0;
        m_ConsecDownhill = 0;
        m_ConsecCurve = 0;
        m_UphillFlt *= 0.9f;
        m_DownhillFlt *= 0.9f;
        m_CurveFlt *= 0.9f;
        m_LastSpeedFactor = 1.0f;
        return;
      }
    
      // Calculate Slope directly
      const float s = m_DirN[m_SplineIdx].z;
    
      // Calculate Curvature directly
      float avgCurv = 0.0f;
      int curvSamples = 0;
      const int splineSz = (int)m_Spline.size();
    
      if (splineSz >= 3)
      {
          const int maxCenter = splineSz - 2;
          CVertex* splinePtr = m_Spline.data();
          int si, center;
          float c;
    
          for (si = 0; si < 3; ++si)
          {
              center = m_SplineIdx + si;
              if (center < 1) center = 1;
              else if (center > maxCenter) center = maxCenter;
    
              c = Spline::LocalCurvature(splinePtr[center - 1], splinePtr[center], splinePtr[center + 1]);
              if (c > 0.0f) { avgCurv += c; ++curvSamples; }
          }
    
          if (curvSamples) avgCurv /= (float)curvSamples;
      }
    
      const float absS = fabsf(s);
      float factor = 1.0f;
    
      if (absS > SFF_EFFECT_THR)
      {
        float eff = absS - SFF_EFFECT_THR;
        if (s > 0.0f)                                                          // Bergauf: langsamer
        {
          if (eff > SFF_MAX_UP_EFF) eff = SFF_MAX_UP_EFF;
          factor -= eff * m_UphillFlt;
        }
        else                                                                   // Bergab: schneller
        {
          if (eff > 0.10f) eff = 0.10f;
          factor += eff * m_DownhillFlt;
        }
      }
    
      if (avgCurv > 1e-5f) ++m_ConsecCurve; else if (m_ConsecCurve > 0) --m_ConsecCurve;
    
      const float invUp = 1.0f / (float)SFF_CONSEC_FULL_UP;
      const float uphillTarget = (m_ConsecUphill > 0) ? (m_ConsecUphill * invUp) : 0.0f;
      const float downhillTarget = (m_ConsecDownhill > 0) ? (m_ConsecDownhill * invUp) : 0.0f;
      const float curveTarget = (avgCurv > 0.0f) ? (avgCurv / (avgCurv + 0.003f) * 1.5f) : 0.0f;
    
      m_UphillFlt += (uphillTarget - m_UphillFlt) * ((uphillTarget > m_UphillFlt) ? SFF_RISE : SFF_DECAY);
      m_CurveFlt += (curveTarget - m_CurveFlt) * ((curveTarget > m_CurveFlt) ? SFF_RISE : SFF_DECAY);
      m_DownhillFlt += (downhillTarget - m_DownhillFlt) * ((downhillTarget > m_DownhillFlt) ? SFF_RISE : SFF_DECAY);
    
      if (m_CurveFlt > 0.0f)                                                   // Kurven: langsamer
      {
        float tmp = 1.0f - SFF_CURVE_MAX_RED * (m_CurveFlt * (1.0f + 0.65f * m_CurveFlt) * (1.0f + 0.75f * m_CurveFlt));
        if (tmp < 0.1f) tmp = 0.1f;
        factor *= tmp;
      }
    
      if (factor < 0.25f) factor = 0.25f;
      else if (factor > 1.5f) factor = 1.5f;
    
      const float smooth = (factor < m_LastSpeedFactor) ? SFF_SPEED_SMOOTH_DOWN : (SFF_SPEED_SMOOTH_UP / (1.0f + m_Speed * SFF_SPD_SMOOTH_SCL));
      m_LastSpeedFactor += (factor - m_LastSpeedFactor) * smooth;
    }
    
    void CSplineObjFollow::CalcSpeedA()
    {
      float factor = 1.0f;
      const int dirSz = (int)m_DirN.size();
    
      if (dirSz >= 2 && m_SplineIdx >= 0)
      {
        float maxProxy = 0.0f;
        const CVertex *dirPtr = m_DirN.data();
    
        for (int i = 0; i < 3; ++i)
        {
          int pidx = m_SplineIdx + i;
          if (pidx < 1) pidx = 1;
          else if (pidx >= dirSz) pidx = dirSz - 1;
    
          int p0 = (pidx > 0) ? pidx - 1 : 0;
          float d = dirPtr[p0].Dot(dirPtr[pidx]);
          if (d > 1.0f) d = 1.0f;
          else if (d < -1.0f) d = -1.0f;
    
          float proxy = 1.0f - d;
          if (proxy > maxProxy) maxProxy = proxy;
        }
    
        const float curveEffect = (maxProxy > 0.0f) ? (maxProxy / (maxProxy + 0.001f)) : 0.0f;
        m_CurveFlt += (curveEffect - m_CurveFlt) * ((curveEffect > m_CurveFlt) ? SFF_RISE : SFF_DECAY);
        factor = 1.0f - SFF_CURVE_MAX_RED * (curveEffect * (1.0f + 0.75f * m_CurveFlt));
      }
    
      if (factor < 0.15f) factor = 0.15f;
      else if (factor > 1.5f) factor = 1.5f;
    
      float alpha = 0.20f + 0.55f * (1.0f - factor);
      if (alpha > 1.0f) alpha = 1.0f;
      if (factor < m_LastSpeedFactor) alpha = alpha * 0.9f + 0.5f * (1.0f - alpha);
    
      m_LastSpeedFactor += (factor - m_LastSpeedFactor) * alpha;
    }
    

    Diese Verfolgung von Tracks wird also in separaten Threads berechnet, und für jede Lok durchlaufen, da gibt es nicht die geringsten Probleme mit der Render Geschwindigkeit , es wird also kein Normal benötigt um die Geschwindigkeit auf den Dynamischen Tracks zu erhalten, die allerdings über normal vektoren verfügen, jedoch nie von der Mechanik abgefragt werden, die Tracks haben auch eine Beschreibung über den sog. TrackFragmentGenerator:

    /*
    ==========================================================
    TrackFragment Eigenschaften & Geometrie-Regelwerk
    ==========================================================
    
    1) Grundstruktur (RNA1):
       - Ein TrackFragment besteht aus mehreren Einzelteilen: Balast, Slicer (Schienen), Sleeper (Schwellen).
       - Die Geometrie wird entlang einer Eingangsspline (VertexLst) erzeugt und verteilt.
       - Jeder Einzelteiltyp hat eigene Parameter und Farben.
    
    2) Balast (RNA1):
       - Wird als U-Profil ohne Boden generiert.
       - Besteht aus Seitenwänden und Dach, keine Unterseite.
       - Die Breite und Höhe sind abhängig vom Maßstab (scale).
       - Die Balast-Spline wird mit niedriger Dichte (Subsampling) erzeugt.
       - Speicherbedarf steigt mit Dichte und Länge der Spline.
    
    3) Slicer (Schienen) (RNA1):
       - Werden als U-Profile ohne Boden generiert.
       - Bestehen aus Seitenwänden und Dach, keine Unterseite.
       - Slicer sind schmaler und höher als der Balast.
       - Es werden zwei Slicer erzeugt: linke und rechte Schiene, jeweils entlang der Spline versetzt.
       - Slicer erhalten eine eigene Farbe für Seiten und Dach.
       - Subsampling reduziert Speicher und Rechenlast.
    
    4) Sleeper (Schwellen) (RNA1):
       - Werden als Quader erzeugt, keine U-Profile.
       - Liegen quer zur Spline und verbinden die beiden Slicer.
       - Die Platzierung erfolgt gleichmäßig entlang der Spline, am Anfang, Ende und dazwischen.
       - Sleeper erhalten eigene Farben für Seiten und Oberseite.
       - Anzahl und Abstand beeinflussen die Komplexität.
    
    5) Geometrie-Erzeugung (RNA1):
       - Die Methode GenerateVbo erzeugt die Einzelteile entlang der Spline.
       - Für Balast und Slicer wird die Spline mit unterschiedlichen Schrittweiten abgetastet (Subsampling).
       - Die Geometrie wird als Vertex-, Normal-, Color- und Index-Arrays für das VBO erzeugt.
       - Datenfluss: Spline -> Sampling -> Einzelteil -> VBO.
    
    6) Farben und Materialien (RNA1):
       - Jede Komponente (Balast, Slicer, Sleeper) erhält eigene Farben für Seiten und Dach/Oberseite.
       - Die Farben werden beim Erzeugen der Vertices zugewiesen.
       - Farbwechsel im Code beachten.
    
    7) VBO-Handling (RNA1):
       - Die erzeugten Geometrien werden in einem VBOSet gespeichert.
       - Nach der Erzeugung wird das VBO für die Darstellung vorbereitet.
       - Speicherbedarf und Performance hängen von der Anzahl der Vertices und Indices ab.
    
    8) Besonderheiten (RNA1):
       - Die Geometrie ist so ausgelegt, dass keine Bodenflächen für U-Profile entstehen.
       - Die Verteilung und Ausrichtung der Einzelteile erfolgt dynamisch entlang der Eingangsspline.
       - Fehlerquellen: Indizierung, Subsampling, falsche Parameter.
    
    9) Komplexitätsfaktoren (KI-relevant):
       - Anzahl und Dichte der Einzelteile (Balast, Slicer, Sleeper).
       - Länge und Form der Eingangsspline.
       - Subsampling-Parameter und Profilgrößen.
       - VBO-Speicherbedarf und Render-Performance.
       - Korrekte Indizierung und Farbzuteilung.
       - Robustheit gegen fehlerhafte Eingabedaten.
    
    10) Speicherverwaltung und Vektorzugriffe (RNA1):
       - Alle Geometrie-Daten werden in std::vector gespeichert.
       - Jeder push/remove/clear/erase-Aufruf ruft automatisch die Destruktoren der enthaltenen Objekte auf.
       - Dadurch werden Ressourcen immer korrekt freigegeben.
       - Speicherlecks sind ausgeschlossen, solange keine rohen Zeiger verwendet werden.
    
    11) LOD-Rendering für entfernte Fragmente (RNA1):
       - Ab einer Entfernung von TRACK_LOD_DIST werden weniger Indices im VBO gezeichnet.
       - Ein visueller Hinweis (z.B. "LOD aktiv") wird rechtsbündig am Fragment angezeigt.
       - Die LOD-Distanz ist als TRACK_LOD_DIST im Code definiert.
       
       LOD-Priorität (VBO-Reihenfolge, Index 0 = höchste Priorität):
       +-------+------------------+---------------+---------------------------+
       | Phase | Komponente       | LOD-Priorität | Beschreibung              |
       +-------+------------------+---------------+---------------------------+
       |   1   | Gauges           | Höchste       | Lichttafel (4 Lichter)    |
       |   1   | Dipper           | Höchste       | Signalflügel              |
       |   2   | Switch-Cube      | Hoch          | Weichen-Hitbox            |
       |   3   | Terminal         | Hoch          | Bahnsteig-Geometrie       |
       |   4   | Signal-Mast      | Mittel        | Stange + Sockel           |
       |   5   | Prellbock        | Mittel        | Buffer Stop               |
       |   6   | Ballast          | Niedrig       | Schotter (viele Faces)    |
       |   6   | Slicer           | Niedrig       | Schienen (viele Faces)    |
       |   6   | Sleeper          | Niedrig       | Schwellen (viele Faces)   |
       +-------+------------------+---------------+---------------------------+
       
       Bei LOD-Reduktion werden zuerst Sleeper/Slicer/Ballast weggelassen,
       Gauges und Dipper bleiben bis zuletzt sichtbar.
    
    12) Switch-Cube für Weichen (RNA2):
       - Wird nur bei TYPE_SWITCH_TRK erzeugt.
       - Besteht aus einem Würfel (Cube) und einem Sockel (Pyramiden-Stumpf) neben dem Gleis am Ende der Spline.
       - Position: Neben dem Balast in Fahrtrichtung, versetzt um die halbe Balast-Breite plus zusätzlichen Abstand.
       - Größe: Würfel hat scale * 1.6f Größe, Sockel ist doppelt so breit.
       - Farben: Würfel in Slicer-Farben (Grün=Open, Rot=Closed), Sockel in Balast-Farben.
       - Zweck: Visuelle Darstellung von Weichen-Switches mit Statusanzeige (Open/Closed).
       - Bounding Box: Separate m_SwitchBBoxMin/Max für Kollision oder Selektion.
    
    13) Signal-Mast für Weichen & Strecken (RNA3):
       - Wird bei TYPE_SWITCH_TRK (automatisch) oder via SnapIn (manuell) erzeugt.
       - Besteht aus mehreren Komponenten:
         a) Sockel: Pyramiden-Stumpf auf Terrain-Höhe
         b) Mast: Zwei vertikale Streben (Leiter-Stil) mit Sprossen
         c) Kopfstück: Horizontaler Balken am oberen Ende
         d) Ausleger: Horizontale Stäbe zur Seitenstabilisierung
         e) Dipper: Beweglicher Signal-Flügel (Formsignal)
         f) Gauge: Lichttafel mit 4 Signallichtern (Hp0/Hp1/Hp2)
       - Position:
         * Weichen: Automatisch in der Mitte der Spline.
         * Strecken: An definierter SnapIn-Position (dotIdx).
       - Dipper-Zustände:
         * TYPE_SIGNAL_OPEN gesetzt: Dipper horizontal (Hp0 = Halt)
         * TYPE_SIGNAL_OPEN nicht gesetzt: Dipper nach unten (Hp1 = Fahrt frei)
       - Farben: Mast in Balast-Farben, Dipper rot mit weißer Füllung und Ring.
       - Bounding Box: Separate m_SignalBoxes für Klick-Erkennung (umschließt gesamten Mast).
       - Debug-Anzeige: Weiße Box, roter Text "Hp0"/"Hp1".
       - Lok-Steuerung: Signal-Status wird in SnapIn.flags gespeichert.
    
    14) Prellbock / Buffer Stop (RNA4):
       - Wird automatisch bei TYPE_PILLER_TRK erzeugt (Track mit offenem Ende).
       - Erkennung: forwardFragmentId == -1 ODER backwardFragmentId == -1.
       - Position: Am Gleisende, versetzt um 3 * SLEEPER_PITCH nach hinten.
       - Struktur:
         a) Gleis-Extension: Echtes Gleis (Balast/Slicer/Sleeper) vom Ende bis zum Prellbock.
         b) Schraege Stuetzen: 2x 45-Grad Hypotenuse (links/rechts auf Schienen).
         c) Vertikale Kateten: 2x senkrechte Pfosten hinter den Stuetzen.
         d) Querbalken oben: Horizontaler Balken zwischen den Kateten-Spitzen.
         e) Querbalken unten: Horizontaler Balken am Fuss der Stuetzen.
       - Doppelgleis: 2 separate Prellboecke mit Gleisabstand versetzt.
       - Verkehrsvertrag: Am Prellbock dreht nur die Fahrtrichtung der Lok. Ihre
         m_TrackSide bleibt unveraendert; der seitliche Render-Offset wird gespiegelt.
         Bei einem Doppelgleis-Hostwechsel wird zusaetzlich die Weltposition fuer
         die Seitenkontinuitaet ausgewertet, damit Weichen keinen Spurwechsel erzeugen.
       - Farben: Stuetzen grau, Querbalken in Sleeper-Farben.
       - Rekursionsschutz: TYPE_PILLER_TRK wird fuer Extension-Spline entfernt.
    
    15) Terminal / Bahnsteig (RNA5):
       - Länge basiert auf Zuglänge: TERMINAL_WAGON_LEN × TERMINAL_MAX_WAGONS = 15000 units
       - 1 Waggon/Lok = 1500 units, Max 10 Waggons am Bahnsteig
       - Z-Ebene: Einheitliche Höhe (Mittelwert aller Dots) - keine Wellen!
       - Zwei Signale (A + B) an den Enden für Face-to-Face Steuerung.
       - Dynamische Verkürzung wenn Track zu kurz ist.
    
    16) Verkehr & Fahrplaner (RNA6):
       - Die Basis-Verkehrslogik ist aktiv: physische Doppelgleis-Seiten,
         Bremsen und Disaster-Erkennung laufen in GLSceneTraffic.
       - Ein globaler Fahrplaner/Scheduler über dieser Basis bleibt geplant.
       - Er nutzt RNA1-5 (Gleise, Weichen, Signale, Prellboecke, Terminals)
         fuer Zeitsteuerung und Automatisierung von Weichen/Signalen.
    */
    
    

    nicht in jedem Frame machen. Die Geometrie der Lok und Waggons bedingt ja einen minimalen Krümmungsradius.
    Tatsächlich wird es sogar öfters berechnet als pro jedem Frame, das geht mit bis zu 20 tausend Iterationen also 20Khz
    in den Threads, die Loks fahren seidig und extrem Pixel genau auf den Gleiskörpern.
    Durch die Translationen für die Lok -Mesh Körper werden diese exact an der Spline geführt dazu wird für die Loks eine Pivot-Translation verwendet:

    void CGLmatrix::TranslatePivotXYZTrack(const CVertex& world, CVertex& pivot, const CVertex& forward, const CVertex& right, const CVertex& up)
    {
    	float* m = m_m0;
    
    	// Snapshot original orientation (R0) and translation (t0)
    	const float r0x = m[0],  r0y = m[1],  r0z = m[2];
    	const float r1x = m[4],  r1y = m[5],  r1z = m[6];
    	const float r2x = m[8],  r2y = m[9],  r2z = m[10];
    	float       tx  = m[12], ty  = m[13], tz  = m[14];
    
    	// Phase 1: t1 = t0 + R0 * (world + pivot)
    	const float v0x = world.x + pivot.x;
    	const float v0y = world.y + pivot.y;
    	const float v0z = world.z + pivot.z;
    	tx += r0x * v0x + r1x * v0y + r2x * v0z;
    	ty += r0y * v0x + r1y * v0y + r2y * v0z;
    	tz += r0z * v0x + r1z * v0y + r2z * v0z;
    
    	// Phase 2: DIREKTE Matrix-Orientierung aus Spline-Basis (statt Euler-Rotation)
    	// R' = R0 * SplineBasis (Forward, Right, Up direkt verwenden)
    	m[0] = r0x * forward.x + r1x * forward.y + r2x * forward.z;
    	m[1] = r0y * forward.x + r1y * forward.y + r2y * forward.z;
    	m[2] = r0z * forward.x + r1z * forward.y + r2z * forward.z;
    	
    	m[4] = r0x * right.x + r1x * right.y + r2x * right.z;
    	m[5] = r0y * right.x + r1y * right.y + r2y * right.z;
    	m[6] = r0z * right.x + r1z * right.y + r2z * right.z;
    	
    	m[8] = r0x * up.x + r1x * up.y + r2x * up.z;
    	m[9] = r0y * up.x + r1y * up.y + r2y * up.z;
    	m[10] = r0z * up.x + r1z * up.y + r2z * up.z;
    
    	// Phase 3: t' = t1 + R' * (-pivot)
    	tx += m[0] * (-pivot.x) + m[4] * (-pivot.y) + m[8]  * (-pivot.z);
    	ty += m[1] * (-pivot.x) + m[5] * (-pivot.y) + m[9]  * (-pivot.z);
    	tz += m[2] * (-pivot.x) + m[6] * (-pivot.y) + m[10] * (-pivot.z);
    	m[12] = tx; m[13] = ty; m[14] = tz; // keep m[15] as is
    }
    
    

    Und für normale nicht dem Track folgender Objekte eine Pivot normale Translation (Beides Teil der GL Engine wo jede Matrix wenn sie fertig im Speicher liegt nur einmal per Frame an GL gesendet wird was zu wahnsinnigen Ablaufraten führt:

    void CGLmatrix::TranslatePivotXYZ(const CVertex& world,  CVertex& pivot, const CVertex& rot)
    {
    	float* m = m_m0;
    
    	// Snapshot original orientation (R0) and translation (t0)
    	const float r0x = m[0],  r0y = m[1],  r0z = m[2];
    	const float r1x = m[4],  r1y = m[5],  r1z = m[6];
    	const float r2x = m[8],  r2y = m[9],  r2z = m[10];
    	float       tx  = m[12], ty  = m[13], tz  = m[14];
    
    	// Phase 1: t1 = t0 + R0 * (world + pivot)
    	const float v0x = world.x + pivot.x;
    	const float v0y = world.y + pivot.y;
    	const float v0z = world.z + pivot.z;
    	tx += r0x * v0x + r1x * v0y + r2x * v0z;
    	ty += r0y * v0x + r1y * v0y + r2y * v0z;
    	tz += r0z * v0x + r1z * v0y + r2z * v0z;
    
    	// Phase 2: R' = R0 * R(rot) – RotXYZ(rot) on the basis columns
    	const float cX = CCalculate::LutCos(rot.x); const float sX = CCalculate::LutSin(rot.x);
    	const float cY = CCalculate::LutCos(rot.y); const float sY = CCalculate::LutSin(rot.y);
    	const float cZ = CCalculate::LutCos(rot.z); const float sZ = CCalculate::LutSin(rot.z);
    
    	// Start from current m (R0) and apply X, then Y, then Z exactly like RotXYZ
    	float x0, y0, z0, x1, y1, z1, x2, y2, z2;
    	// X-axis
    	x0 =  m[0];  y0 =  m[1];  z0 =  m[2];
    	x1 =  m[4] * cX + m[8] * sX;   y1 =  m[5] * cX + m[9] * sX;   z1 =  m[6] * cX + m[10] * sX;
    	x2 =  m[4] * -sX + m[8] * cX;  y2 =  m[5] * -sX + m[9] * cX;  z2 =  m[6] * -sX + m[10] * cX;
    
    	m[0] = x0; m[1] = y0; m[2] = z0;
    	m[4] = x1; m[5] = y1; m[6] = z1;
    	m[8] = x2; m[9] = y2; m[10] = z2;
    
    	// Y-axis
    	x0 =  m[0] * cY + m[8] * -sY;  y0 =  m[1] * cY + m[9] * -sY;  z0 =  m[2] * cY + m[10] * -sY;
    	x1 =  m[4];                    y1 =  m[5];                     z1 =  m[6];
    	x2 =  m[0] * sY + m[8] * cY;   y2 =  m[1] * sY + m[9] * cY;   z2 =  m[2] * sY + m[10] * cY;
    
    	m[0] = x0; m[1] = y0; m[2] = z0;
    	m[4] = x1; m[5] = y1; m[6] = z1;
    	m[8] = x2; m[9] = y2; m[10] = z2;
    
    	// Z-axis
    	x0 =  m[0] * cZ + m[4] * sZ;   y0 =  m[1] * cZ + m[5] * sZ;   z0 =  m[2] * cZ + m[6] * sZ;
    	x1 =  m[0] * -sZ + m[4] * cZ;  y1 =  m[1] * -sZ + m[5] * cZ;  z1 =  m[2] * -sZ + m[6] * cZ;
    	x2 =  m[8];                    y2 =  m[9];                     z2 =  m[10];
    
    	m[0] = x0; m[1] = y0; m[2] = z0;
    	m[4] = x1; m[5] = y1; m[6] = z1;
    	m[8] = x2; m[9] = y2; m[10] = z2;
    
    	// Phase 3: t' = t1 + R' * (-pivot)
    	tx += m[0] * (-pivot.x) + m[4] * (-pivot.y) + m[8]  * (-pivot.z);
    	ty += m[1] * (-pivot.x) + m[5] * (-pivot.y) + m[9]  * (-pivot.z);
    	tz += m[2] * (-pivot.x) + m[6] * (-pivot.y) + m[10] * (-pivot.z);
    	m[12] = tx; m[13] = ty; m[14] = tz; // keep m[15] as is
    }
    

    einfacher sein könnte, dem User das Feintuning zu überlassen
    Der braucht ein klares Spielerlebnis , das da beim gleise Legen irgendwelche Kapriolen passieren soll der nicht handhaben,
    es spielen ja auch viele Kinder oder bei großen Gleisanlagen währ es zu frikelig, der Editor unterstellt dem User ganz klare Edit Regeln die unbedingt von der Software sichergestellt werden müssen so wie bei einem CAD Programm.

    50 Spieler "MMO" ist schon nen hartes Ziel.
    Auf jeden Fall, die können sogar Server mieten und ihr eigenes Terrain (Ressourcen) über Tunnels an das Maingrid anschließen, das läuft dann aber über Linux auf Mietservern, das Thema alleine sprengt schnell den Rahmen da muss man aufpassen, es beginnt in kürze mit der Fahrplan Erstellung und dem Zug-Roster der die Wagongs koppelt(folge Jahr) . Sicher kommen dort Hacker und Einkreiser usw. muss man sehen das sich das selber verwaltet. UDP SSL Kodierter Transfer mal sehen das ist noch ein Weilchen hin. Aber die Kommunikation ist im Prinzip klar, läuft dann zuerst auf ner VM am Nebentisch...

    Viel Erfolg damit! Ich glaub da gibts auch sicher einige Foren und Communities für derartige Spiele.
    Na erstmal noch nicht , in dem Zustand interessiert das nur Experten, es gibt ja noch kein Game, in Foren habe ich bis heute noch keinen gehört dem das gefällt 🙂 Da bist Du der erste mit dem ich drüber Chatten kann 🙂
    Das OpenTTD ist eine andere Liga, da will ich nicht hin, ich mache ein ganz spezielles Railroad wo der User Signale weichen Bahnhöfe auch über Lua -Skript kontrollieren kann, also Stichwort alles ist volldynamisch und generisch.

    Ego-Perspektive (Lokführer)
    Genau ich habe einen Gleistracker der mit der Kamera alle bestehnden Gleise abfährt (Neben der Zugverfolgung) Das jetzt schon ein Wahnsinns ScreenBlanker !

    Danke für deine Aufmerksamkeit
    Ich berichte mal immer gerne über solche Sachen 🙂
    Gruß Karsten


  • Gesperrt

    Dieser Beitrag wurde gelöscht!


  • @Sternlicht4384 Hallo Sternlicht,

    ja Du sagst es sehr schnell kann man mit so einem Riesenprojekt in die Eskalation wandern, ich bin dabei nun nichts neues hinzuzufügen, sondern die Stabilität und Prozesse innert der komplexen Vorgänge zu stabilisieren damit eine Veränderung nicht an anderer Stelle Auswirkungen haben kann muss alles in Aktionstiefen verankert sein die sich nicht gegenseitig berühren.
    Zb. Das ein schneller Zug einen langsamen einholt, nun muss der rechtzeitig dessen Tempo übernehmen um nicht aufzufahren. Oder wenn ein Terminal(Bahnhof) bereits belegt ist, der Zug am Eingangssignal wartet bis der andere Zug das Terminal verlassen hat, da gibt es viele Szenarien an Signalen die vor einer Kollision erkannt sein müssen. Es gibt auch in der Demo noch viele Gleis Anschluss Unpässlichkeiten, außerdem fehlen die Tunnels noch. Es gibt also Doppelgleise Single Gleise Taper(Reduzierstück) Signale Terminals Weichen die man stellen kann über Lua oder händisch. Ich brauch das ganze restliche Jahr nur zur Optimierung und Sicherstellung der Abhängkeitsisolation. Das wird auch noch einige Jahre so weiter gehen bis wir old Sid Mayer das Wasser reichen werden, das müssen echte Programmierer gewesen sein, die das mit Assembler auf dem Amiga oder 286 ziger implemtiert haben.
    Wenn das alles läuft und die Vorlage Assets plausible sind (siehe Binary all die meshes die dann optimiert mit drehenden rädern als Anim gespeichert werden müssen in Foldern mit einzelnen 3DS Dateien.. Haufen Holz alles für einen zu viel.

    Danke für deine Hinweise, vielleicht machst'e später mit, wenn es mehr zu bieten hat als aktuell ? 🙂
    Grüße aus Preußen
    K.


  • Mod

    @KahnSoft : Ich muss dich leider enttäuschen. Das wird ein Spammer sein. Leute melden sich nicht in Foren neu an, um dann als ersten und einzigen Beitrag solche Texte zu verfassen. Da wird in den nächsten Tagen ein Edit kommen, der dann Werbung für ein Kasino macht.

    @Sternlicht4384 : Für die 0.1% Chance, dass du kein Bot sein solltest: Entschuldigung. Ich bin ja auch immer fair und warte, bis der Werbelink tatsächlich erscheint, bevor ich jemanden sperre. Falls ich also Unrecht haben sollte, ist noch nichts passiert.



  • @SeppJ Ich hatte auch schon so den Verdacht... aber lass uns erst warten, was passiert. Offenbar scheinen Programmierer genau die Zielgruppe für (Online-) Kasinos zu sein.

    Aber mal btt... Zu den MFCs in C++ kann ich nicht all zu viel sagen.



  • @KahnSoft Bin gerade beim Aufräumen wieder über mein altes Projekt mit dem Heightmap-Terrain-LOD gestolpert und dachte ich kann bei der Gelegenheit auch mal ein paar Screenshots posten, um das Prinzip zu verdeutlichen. Das soll nicht heißen, dass es das Nonplusultra ist - ich will das nur als potentielle Inspiration in den Raum stellen.

    Heightmaps haben ja den Vorteil, dass man sie mit einer simplen Bild 1: Schachbrett-Triangulierung rendern kann, bei der die Vertices dann mit senkrecht zur Fläche den Werten der Heightmap moduliert werden.

    Die Idee des "kontinuierlichen" LOD das ich implementiert habe ist, dass die Anzahl der Dreiecke mit jeder LOD-Stufe vervierfacht wird und dabei aus einer quadratischen "Zelle" mit zwei durch eine diagonale Kante verbundenen Dreiecken vier solcher Zellen mit 8 Dreiecken werden. Um den Übergang "fließend" zu machen wird zwischen der einen Zelle und den vier Zellen "gemorpht", was bei dieser dieser Methode bedeutet, den zentralen Vertex einer Vierer-Zelle in Richtung eines der Eckpunkte zu verschieben (Bild 2: LOD-Übergangs-Moprhing) bis 6 der Dreiecke verschwunden sind. Ab dem Punkt wird dann das niedriger aufgelöste Mesh verwendet.

    Diesen Übergang kann man dann von der Distanz zur Kamera abhängig machen, was dann so aussieht:

    Bild 3: Kontinuierliches LOD auf Mesh

    Oder mit aufmodulierter Heightmap:

    Bild 4: Kontinuierliches LOD mit Heightmap

    Und texturiert und mit simpler Beleuchtung:

    Bild 5: Kontinuierliches LOD mit Texturen

    Ich finde die Methode insofern ganz gut, weil das Landschafts-Mesh nur langsam in eine höhere oder niedrigere Auflösung "morpht", wenn man sich nähert oder entfernt. Die Veränderungen der Geometrie sind dabei nicht so offensichtlich, als wenn man ab einer gewissen Distanz direkt in eine andere Auflösung "springt".



  • Hallo Finnegan,
    ah so meinst du das der Lod mit der Höhe zunimmt, tatsächlich habe ich das hier so, das es Grundplatten(Chunks) gibt, die mit zunehmender Entfernung weniger Dreiecke zeigen. Die Höhe der Hügel hat hier keine Kopplung mit der Grid-Auflösung.
    Eine Highmap kam leider für das Projekt nicht in Frage weil die Dimension der Karte fast eine Millionen Dreiecke Seitenlänge aufweist. Mit dem Chunks erreiche so trotz der riesigen Dimension bis zu 1Khz Bildwiederholraten, denn ich muss auch noch Züge und Gleise rendern, diese unterliegen auch einem Lod, der ist einfach je weiter ein Mesh entfernt ist desto weniger seiner Dreiecke werden gerendert, die kleinen stücke fliegen zuerst aus der Pipe , und die größten teile zuletzt.
    https://www.flexxvision.de/Download/Chunks.jpg
    So kann ich unendlich viele Gleise Loks und Terrains rendern, das Terrain wurde Z-Seitig mit der Perlin Noise angehoben was diese Hügel produziert, da kann ich dann generisch nich mehr machen, allerdings verfügt die Engine auch über Highmap Terrains.
    https://www.flexxvision.de/Download/Highmap.jpg
    Hier im Bild in einem anderen Kontext ohne Lod's
    Ja die Weise die Dreiecke zu verdichten bei unterschiedlichen Z -Höhen habe ich noch nicht implementiert, das würde sich gut mischen mit den Chunks die im zweiten Bild jedoch nicht eingesetzt werden, da die Größe mit 30Hz Echtzeit Z Variation von einer Kamera kommend schnell genug war.

    Sehr interessant wie Du das umgesetzt hast, obschon das erste Raster ohne Textur viele doppelte Linien aufwies.

    Im Moment mache ich nicht gerade viel am Projekt, mal sehen wann ich wieder loslege.

    Vielen Dank für deine Ausführung , das ganze heißt Vertice Quaternation https://medium.com/@Ksatese/b-spline-terrain-reconstruction-and-quaternion-camera-in-opengl-ec66491c0623

    Naja ist ja bald wieder kalt draußen, und dann hat man mehr musse 🙂
    Gruß
    K aus B.



  • @KahnSoft sagte in Rail Road Tycoon MFC in Action:

    ah so meinst du das der Lod mit der Höhe zunimmt, tatsächlich habe ich das hier so, das es Grundplatten(Chunks) gibt, die mit zunehmender Entfernung weniger Dreiecke zeigen. Die Höhe der Hügel hat hier keine Kopplung mit der Grid-Auflösung.

    Nein, das hat nichts mit der Höhe zu tun, sondern ist auch ein System, das mit zunehmender Entfernung von der Kamera die Grid-Auflösung reduziert. Es würde aber durchaus Sinn machen, auch mit zunehmender Steigung die Auflösung zu erhöhen, bei besonders steilen Anstiegen werden ansonsten einzelne Dreiecke sehr lang gezogen. Das ist aber ein sehr simples Heightmap-Rendering, dass ich da implementiert habe: Die Der Heightmap-Wert an der Position eines Grid-Vertex bestimmt direkt seine Höhe. Das ließe sich sicher noch etwas intelligenter machen, um die Qualität des Renderings zu erhöhen.

    Eine Highmap kam leider für das Projekt nicht in Frage weil die Dimension der Karte fast eine Millionen Dreiecke Seitenlänge aufweist. Mit dem Chunks erreiche so trotz der riesigen Dimension bis zu 1Khz Bildwiederholraten,

    Das Grid-Rendering sollte mit sowas klar kommen dank LOD, aber die Heightmap selbst kann schon ziemlich groß werden. In meinem Fall ist das eine simple 16-Bit Grausttufen-Textur (8-Bit ist eher ungeeignet für Heightmaps, da es wegen der nur 256 Abstufungen unschöne Treppenartefakte erzeugt). Und mit der Methode hast du natürlich recht: Eine 1000000x1000000 Heightmap-Textur mit 2 Byte pro Pixel wäre etwa 1,8TiB groß.

    Alternativ könnte man die Heightmap aber auch prozedural auf der GPU und ebenfalls in einer entfernungsabnhänigen Auflösung generieren, dann sollte der Speicherbedarf nicht so hoch sein. Das ist ja auch bei dir bereits bereits eine prozedurale Landschaft, wenn ich das mit dem Perlin Noise richtig verstehe.

    Vielen Dank für deine Ausführung , das ganze heißt Vertice Quaternation https://medium.com/@Ksatese/b-spline-terrain-reconstruction-and-quaternion-camera-in-opengl-ec66491c0623

    Ist das die Technik, die du für das Landschaftsrendering verwendest? Wenn ich das beim groben Überfliegen richtig verstanden habe, werden dabei implizite B-Spline-Flächen auf die Höhendaten (Heightmap-Textur oder on-the-fly-generierter Perlin Noise) gefittet und dann daraus die Geometrie generiert. Ich ziehe da keine so klare Grenze und würde das ebenfalls den Heightmap-Techniken zuordnen. Allerdings den fortgeschritteneren, während mein Ansatz eher primitiv ist.

    Es ist auch so, dass die von mir beschriebene Methode für kontinuierliches LOD nicht unbedingt an Heightmaps gekoppelt ist. Die kann man in der einen oder anderen Form eigentlich immer anwenden, wenn man es mit so einer gleichförmigen Schachbrett-Triangulierung zu tun hat, wie das z.B. auch bei deinen Chunks der Fall ist. Es ist halt lediglich eine einfache Methode um verschiedene LOD-Stufen fließend ineinander übergehen zu lassen, anstatt harte Grenzen zu haben.

    Hier is z.B. so eine harte Grenze bei dir (aus dem "Chunks" Screenshot). Was passiert da, wenn man näher an den hinteren Chunk herankommt? Ich vermute mal, der springt von einem Frame auf den anderen in eine höhere Auflösung (?). Die primäre Motivation meines Ansatzes ist nämlich, eben solche Sprünge zu vermeiden und die LOD-Übergange kontinuierlich mit der Kamera "mitwandern" zu lassen und so die auflösungsbedingte Veränderung der Geometrie weitgehend vor dem Betrachter zu verstecken.

    Das LOD wäre damit nicht mehr an die Chunks gebunden, sondern ausschließlich an Kameraposition. Chunks in diesem Übergangsbereich müssen natürlich eine höhere Auflösung haben, damit man z.B. in einem Vertex-Shader diesen Überhang hinein-transformieren kann. Ich denke schon, dass sich dein chunk-basiertes System und so ein kontinuierliches LOD nicht unbedingt ausschließen.

    Aber letztendlich ist das nur ein nicht ganz so wichtiges Detail und du hast gewiss erstmal andere Prioritäten. Auch ist es fraglich, ob diese LOD-Sprünge überhaupt auffallen, so wie du die Landschaft aktuell renderst. Das ist also eher was für weit unten auf der Prioritätsliste - aber vielleicht ist es ja eine Idee für den letzten Feinschliff irgendwann mal 😉

    Sehr interessant wie Du das umgesetzt hast, obschon das erste Raster ohne Textur viele doppelte Linien aufwies.

    Ich denke das was du mit den "doppelten Linien" sind diese Übergänge hier von einer LOD-Stufe in die nächste. Ja, die sind etwas unschön und sie erzeugen stellenweise ein paar degenerierte Dreiecke. Die treten aber nur in diesen Übergangsbereichen auf und driften dann reissverschlussartig auseinander, wenn man näher herankommt. Diese "doppelten Linien" hat man dort, wo das Gitter z.B. 10% in die höhere Auflösung "gemorpht" ist und die niedrigere Auflösung noch zu 90% dominiert. Die ergeben sich schlicht aus dem Schema, dass ich anwende um zwei LOD-Stufen ineinander zu überführen.

    Vielen Dank für deine Ausführung , das ganze heißt Vertice Quaternation https://medium.com/@Ksatese/b-spline-terrain-reconstruction-and-quaternion-camera-in-opengl-ec66491c0623

    Auch dir vielen Dank. macht Spass sich über sowas auszutauschen und interessant zu sehen, wie du sowas umsetzt. Den verlinkten Artikel werd ich mir nochmal in Ruhe durchlesen. Wenn ich nochmal sowas umsetze, würde ich sicher auch in Richtung NURBS/B-Splines mit GPU-generierterter, prozeduraler Landschaft gehen und vielleicht versuchen, das kontiunuierliche LOD dabei zu integrieren. Auch ist mein Ansatz noch lange nicht perfekt, vor allem wegen dem eher simplistischen "Morphing"-Schema. Das lässt sich sicher noch weiter verfeinern.



  • @Finnegan sagte in Rail Road Tycoon MFC in Action:

    s

    Hi Finnegan ,

    ist das die Technik, die du für das Landschaftsrendering verwendest?
    Nein aber die Seite hat viele Aspekte die man sich ansehen kann, oder integrieren könnte, ist rein sporadisch aus dem Netz gewählt, mein Chunksystem musste ich selber herstellen, und ist von anderen erstmal abgekoppelt .

    Die primäre Motivation meines Ansatzes ist nämlich, eben solche Sprünge zu vermeiden und die LOD-Übergange kontinuierlich mit der Kamera "mitwandern"

    Ja also das Chunksystem basiert ausschließlich auf den Kameraabstand, es ist vollständig flüssig Sprünge sind mit dem Auge nicht erkennbar es hat 5 Detailstufen, in der Ferne werden mehr als 90% der Dreiecke entfernt, und das kann man gar nicht sehen, die Sache läuft sehr feinstufig , und das ist erst der Anfang, durchaus könnte es sein das ich da nochmal dran gehe, aber für den Moment gibt es keine Probleme Artefakte oder Sprünge, auch nicht wenn ich eine Lok verfolge mit hoher Geschwindigkeit. Sie erreichen umgerechnet bis zu 160 Km/h die Zeit Basis und die Lokomotive länge geben diesen Wert der Physischen Geschwindigkeit, das Terrain fliegt wie bei einem Racing an einem vorbei.
    Auch die primitive Mesh reduzierung liefert sehr gute LOD Ergebnisse, allerdings kann ich hier noch mehr fine Tuning später ansetzen, zb ein Haus mit einem Tisch auf dem ein becher steht, der muss natürlich zuerst verschwinden auf Vertex ebene und am ende erst das Dach des Hauses, das ist noch nicht 100% gelöst, allerdings sieht man auch keine Problem beim vergrößern des Kameraabstandes.

    Sehr wichtig war das bei den Gleisen die bestehen aus Balast(Schotterbett) Sleeper(Schwellen) und Slicer (Schienen) hier fliegen zuerst die Schienen raus dann der Balast, und das Schottertet bleibt sehr lange liegen so hat man aus großer Entfernung noch immer die Gleisführung im Bilde. Allerdings wie bei den Meshes werden in der Entfernung immer mehr Dreiecke entfernt.

    Naja nette Spielerei, ich muss nach den Aufräumarbeiten die fast abgeschlossen sind, weiter in die Zugsteuerung investieren was passiert an welchen Signalen Terminals, Weichen Doppelgleise das heißt bei mit RNA1-7 Das wird noch viel Zeit benötigen, schließlich wird das Verhalten auch über Lua gesteuert in Echtzeit.

    Das kann schnell zur Lebensaufgabe mutieren , denn so einfach ist das alles nicht, mal ebend ein Spiel zu bauen , da muss man wirklich viel Motivation immer wieder neu aufbauen, und gerade diskussionen am Thema steigern genau diese.

    In dem Sinne Netter Thread geworden, alles mögliche angesprochen, vielleicht gibt es auch andere die eine Frage dazu haben ?

    Gruß Karsten



  • @KahnSoft sagte in Rail Road Tycoon MFC in Action:

    vielleicht gibt es auch andere die eine Frage dazu haben ?

    Ich finde die Preisgestaltung, im Vergleich zu den (möglichen/realen) Einnahmen, von Spielen wie Cities: Skylines II oder Transport Fever 3 viel zu hoch. Entwicklungskosten und Gewinne stehen einfach in keinem normalen Verhältnis mehr.

    Sprich: Kleinere Entwickler bekommen meist zu wenig, große Studios zu viel vom Kuchen. Es geht nur noch um Gewinnmaximierung...


  • Mod

    @Anon-81275 sagte in Rail Road Tycoon MFC in Action:

    @SeppJ Ich hatte auch schon so den Verdacht... aber lass uns erst warten, was passiert. Offenbar scheinen Programmierer genau die Zielgruppe für (Online-) Kasinos zu sein.

    Und da ist es soweit. Kimonowerbung. Das war zumindest mal was neues.


Anmelden zum Antworten