17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 14:59:19 +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

177 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.