17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 13:19:20 +02:00
Files
maszyna/TURNOUT_SPEC.md
maj00r 707b4de2f4 Editor checkpoint: turnout domain service, web->editor rename, Id-1 templates
Checkpoint of the editor branch so work isn't carried loose across a branch
switch. Builds.

- maj0sted turnout geometry moved into the domain: domain::lay_turnout lays the
  diverging path (pre-blade lead, blade at beta, arc of alfa-beta, frog rail) and
  solve_junction is a thin adapter onto it.
- the maj0sted web/ tree renamed to editor/ (namespace and paths) - there is no
  WASM here, it's plain native C++ the editor calls.
- click places the switch at its start (początek rozjazdu); the branch's leaving
  straight is locked tangent to the frog; switches can be slid along the through
  track and flipped L/P.
- Polish turnout templates carry catalogue length (Id-1-consistent lengths), plus
  blade-angle and pre-blade fields (left 0 until the catalogue figures are in).
- TURNOUT_SPEC.md: the Bezier-based runtime/model contract.
- also folds in other pending branch changes (scenery, tests) to leave a clean tree.
2026-08-27 19:36:55 +02:00

7.8 KiB
Raw Blame History

Rozjazd — kontrakt geometrii dla runtime i modeli

Jak edytor przekazuje rozjazd, a runtime ustawia z tego elementy. Podstawą są krzywe Béziera (kubiczne). To jest warstwa graniczna: wewnątrz edytor liczy łuki i klotoidy (kolejowo dokładne), a na wyjściu przybliża je Bézierami z zadaną tolerancją. Runtime nie zna „typów" rozjazdów — dostaje instancję i sadzi po niej elementy.

Zasada

Runtime nie bazuje na szablonie (Rz 1:9 R190 itd.). Szablony to tylko preset w edytorze, który wypełnia liczby. Każdy rozjazd to instancja o dowolnej geometrii (własny skos, promień, przesunięty początek, łuk koszowy w środku). Runtime dostaje rozwiązaną geometrię i z niej wszystko wyprowadza.

Układ i jednostki

  • metry; krzywe w XY (płaszczyzna planu),
  • rozstaw toru 1435 mm → półrozstaw 0,7175 m,
  • pozy podawane jako pozycja + jednostkowy kierunek (hx, hy) (styczna, „w kierunku jazdy"). Normalna lewa = (-hy, hx),
  • każdy element modelu ma origin w punkcie wstawienia, +X = kierunek jazdy, +Y = w lewo, +Z = w górę. Runtime wstawia go podając tylko pozę — żadnego per-model obrotu.

Instancja rozjazdu

Turnout {
    side          : Left | Right          // strona odgałęzienia (mirror)
    crossing_mark : n                      // skos 1:n  -> dobór odlewu krzyżownicy
    rail_profile  : "49E1" | "60E1"        // -> dobór iglic / profilu szyny
    gauge         : 1.435

    PR : Pose      // początek rozjazdu: styczny do toru zasadniczego
    KR : Pose      // koniec (krzyżownica): kierunek = tor zasadniczy obrócony o alfa
    crossing_angle : alfa = atan(1/n)      // radiany

    blade_angle      : beta                // kąt nagięcia iglicy (kąt zwrotnicowy)
    pre_blade_length : d                   // odcinek przediglicowy: prosty, PR -> ostrze iglicy

    through_axis  : [Bezier...]            // tor zasadniczy przez rozjazd (zwykle prosty)
    diverging_axis: [Bezier...]            // przediglicowy -> iglica(prosta @ beta) -> luk -> prosta -> KR
}

Pose   { x, y, hx, hy }                    // pozycja + jednostkowa styczna
Bezier { p0, p1, p2, p3 }                  // kubiczna, punkty kontrolne w XY (metry)

Tor zasadniczy nie jest dzielony — through_axis jest podany dla kompletności (runtime często ma już tor główny). Rozjazd wnosi przede wszystkim diverging_axis + osprzęt + podrozjazdnice spinające oba tory.

Początek odnogi: odcinek przediglicowy + iglica

Odnoga nie zaczyna się łukiem. To zwrotnica sieczna z iglicą prostą:

  1. odcinek przediglicowy — prosta w kierunku toru zasadniczego (kąt 0), od PR do ostrza iglicy. Tu rozjazd jest jeszcze równoległy do toru głównego; tu siedzi napęd.
  2. iglica — prosta odcięta pod kątem beta („prosta dogięta do prostego"). Odnoga odchodzi od toru zasadniczego dopiero tutaj, o kąt nagięcia iglicy beta.
  3. łuk — promień R, skręca od beta do kąta krzyżownicy alfa, czyli o alfa − beta (nie o całe alfa).
  4. prosta krzyżownicowa — pod kątem alfa, do KR.

Stąd diverging_axis = [przediglicowy] [iglica @ beta] [łuk] [prosta] . Iglice łukowe (styczne, beta ≈ 0, ostrze wtopione w łuk) to inny wariant — na razie modelujemy iglicę prostą.

Model: zespół iglic kotwiony w ostrzu (PR + pre_blade_length wzdłuż toru głównego), zorientowany wzdłuż toru zasadniczego, nadaje beta; długość iglicy jest po stronie modelu.

Bézier — definicja i próbkowanie

Kubiczna:

B(t)  = (1-t)^3 p0 + 3(1-t)^2 t p1 + 3(1-t) t^2 p2 + t^3 p3,   t in [0,1]
B'(t) = 3(1-t)^2 (p1-p0) + 6(1-t)t (p2-p1) + 3t^2 (p3-p2)
  • pozycja = B(t), kierunek = normalize(B'(t)), heading = atan2(B'y, B'x).
  • styczna na końcach: w t=0 wzdłuż p1-p0, w t=1 wzdłuż p3-p2 — dlatego uchwyty p1, p2 leżą na stycznych toru w węzłach,
  • kolejne segmenty są G1 (styczne się zgadzają) — geometria edytora jest styczna z konstrukcji, więc łączenia są gładkie.

Uwaga o parametryzacji: t nie jest długością łuku. Do równego rozstawu podrozjazdnic/podkładów trzeba reparametryzować po długości: całkować |B'(t)| (albo próbkować adaptacyjnie) i szukać t dla zadanej odległości. Nie rozstawiaj elementów po stałym kroku t.

Skąd biorą się Béziery (aproksymacja)

Reguła: jeden element osi = jeden Bézier. Bez dzielenia. Węzły wyznaczają p0/p3 i styczne w węzłach, uchwyty leżą na tych stycznych:

  • prosta → p1, p2 na linii między p0 a p3 (uchwyty w 1/3 długości).

  • łuk kąta theta, promień R → uchwyty wzdłuż stycznych o długości

    k = (4/3) * R * tan(theta/4)
    

    Dla kątów rozjazdowych (theta < 8°) błąd jest rzędu ułamka milimetra.

  • klotoida (krzywa przejściowa, jeśli użyta) → też jeden Bézier: p0, p3 i styczne z końców klotoidy, uchwyty na stycznych w 1/3 cięciwy. Kubiczna nie odda liniowo zmiennej krzywizny co do joty, ale przy długościach przejściówek w rozjeździe odchyłka jest mała i to akceptujemy — jeden segment, bez podziału.

Styczne w węzłach są wspólne dla sąsiednich elementów, więc Béziery łączą się G1.

Szyny — krzywe przesunięte

Szyny to osie przesunięte o ±0,7175 m wzdłuż normalnej lewej. Nie trzymaj ich jako osobnych Bézierów — przesunięcie Béziera nie jest Bézierem. Runtime próbkuje pozę osi i offsetuje per-próbka:

lewa  = B(t) + (-hy, hx) * 0.7175
prawa = B(t) - (-hy, hx) * 0.7175

Na promieniach rozjazdowych rozjeżdżanie się offsetu jest pomijalne.

Podział elementów

Ciągłe — sadzone z pozy wzdłuż osi (zero zależności od typu):

  • szyny — po obu osiach, offset jak wyżej; potrzebny profil przekroju,
  • podrozjazdnice — po długości (reparametryzacja!), prostopadle do dwusiecznej obu osi, wydłużane w strefie iglic i krzyżownicy tak, by objąć oba tory. Parametryczne (skalowana długość) albo zestaw dyskretny — do ustalenia.

Dyskretne odlewy — dobierane parametrem, nie nazwą szablonu:

  • krzyżownica — fizyczny odlew o kącie alfa; dobierana po crossing_mark (skos 1:n), wstawiana w KR, ustawiona pod crossing_angle,
  • iglice (zwrotnica) — dobierane po rail_profile (i ew. R), wstawiane w PR,
  • kierownice — dowiązane do KR,
  • napęd + latarnia — w PR, odsunięte w bok o ustalony offset.

Runtime: geometrię szyn/podrozjazdnic robi z Bézierów, a krzyżownicę i iglice wybiera po (crossing_mark, rail_profile) i wstawia w PR/KR.

Iglice — stany

Rozjazd się przekłada. Iglice potrzebują dwóch stanów (na wprost / na bok) albo animacji przełożenia — do ustalenia z modelarzem. Pozycja bazowa: PR.

Do ustalenia z modelarzem

  1. origin w pionie: główka szyny czy spód podkładu,
  2. profil: 49E1 / 60E1 / oba,
  3. podrozjazdnice: parametryczne czy zestaw dyskretny,
  4. iglice: ruchome (dwa stany / animacja) czy statyczne,
  5. które elementy bierze modelarz, a które generuje runtime.

Do zrobienia po stronie edytora

JunctionGeom ma dziś PR (px,py), KR (fx,fy), kierunek wyjścia (fhx,fhy), długość i osie w postaci polilinii. Do kontraktu trzeba dołożyć:

  • crossing_mark i rail_profile w danych eksportu,
  • blade_angle (beta) i pre_blade_length — dziś lay_turnout kładzie łuk styczny od PR (beta = 0, brak przediglicowego). Trzeba: odcinek przediglicowy (prosty), iglicę prostą pod beta, łuk o alfa − beta, prostą do KR,
  • te dwa parametry w szablonach (turnout_preset) per typ,
  • osie jako Béziery (dziś polilinie) — konwersja łuk/klotoida → kubiczne wg powyższego,
  • jawne oznaczenie „to rozjazd" (nie zwykły łuk), z PR/KR/alfa.