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.
7.8 KiB
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ą:
- odcinek przediglicowy — prosta w kierunku toru zasadniczego (kąt 0), od
PRdo ostrza iglicy. Tu rozjazd jest jeszcze równoległy do toru głównego; tu siedzi napęd. - 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 iglicybeta. - łuk — promień
R, skręca odbetado kąta krzyżownicyalfa, czyli oalfa − beta(nie o całealfa). - prosta krzyżownicowa — pod kątem
alfa, doKR.
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=0wzdłużp1-p0, wt=1wzdłużp3-p2— dlatego uchwytyp1,p2leżą 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,p2na linii międzyp0ap3(uchwyty w 1/3 długości). -
łuk kąta
theta, promieńR→ uchwyty wzdłuż stycznych o długościk = (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,p3i 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 pocrossing_mark(skos 1:n), wstawiana wKR, ustawiona podcrossing_angle, - iglice (zwrotnica) — dobierane po
rail_profile(i ew.R), wstawiane wPR, - 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
- origin w pionie: główka szyny czy spód podkładu,
- profil: 49E1 / 60E1 / oba,
- podrozjazdnice: parametryczne czy zestaw dyskretny,
- iglice: ruchome (dwa stany / animacja) czy statyczne,
- 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_markirail_profilew danych eksportu,blade_angle(beta) ipre_blade_length— dziślay_turnoutkładzie łuk styczny odPR(beta = 0, brak przediglicowego). Trzeba: odcinek przediglicowy (prosty), iglicę prostą podbeta, łuk oalfa − beta, prostą doKR,- 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.