17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02:00
Files
maszyna/TURNOUT_SPEC.md
maj00r 2376e4867b Editor: rebuild the plan library around a solved document, and bend turnouts onto curves
The library was a heap of half-built services - fitting, parallelism, niwelety,
an SVG writer - that never met each other. In their place is one authored
Document and one solver that lays it whole: tracks as element chains, turnouts
placed on them, and a Solution that is rebuilt from scratch every time and never
fed back into the document. What cannot be laid comes back as a diagnostic with
the number in it rather than as silence or a fitted-away error.

Turnouts are dimensioned the way the drawings dimension them. The blade follows
Koc's rys. 5.9 - the curve is theoretically tangent to the opornica at PR, the
blade starts at a with a planed nose of d, and A is where the rail stands u
thick - and a + d derived that way lands on his tablica 5.1. Bending follows
5.3.6: it rotates the triangle OBC about the centre of the turnout, so the
tangent t and the crossing angle stand and both radii follow from them. That is
Koc's (5.7) and (5.8) without coding either, and it puts a symmetrically bent
Rz 49E1-190-1:9 on the catalogue's Rlus 49E1-380,292-1:9. A rozjazd standing on
a curve is bent onto it; one on a track drawn straight can carry a bend of its
own. The szyny laczace take up the difference, because the switch and the frog
are only bent, not re-cut.

The plan panel draws all of it: the catalogue drawing of a type with its
construction points, the theoretical arc dashed where no rail runs, and the
blade nose as the straight it is cut as. Dragging a straight now stops where it
would pull the track out from under a turnout.

File format goes to m0s 6. Versions 3 to 5 still open.
2026-08-29 19:32:46 +02:00

206 lines
9.5 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
length : L = a + b // katalogowa: styczna przednia + tylna
// (Rz 190-1:9: 13,834 + 13,304 = 27,138)
// koniec rozjazdu na torze zasadniczym to
// rzut KR na prostą, krótszy od L
crossing_angle : alfa = atan(1/n) // radiany
pieces : [Piece...] // geometria toru zwrotnego, PR -> KR, wprost z katalogu
blade : { u, w } // grubosc scietego ostrza, szerokosc glowki szyny
through_axis : [Bezier...] // tor zasadniczy przez rozjazd (zwykle prosty)
diverging_axis: [Bezier...] // te same odcinki, przybliżone Bézierami
}
Piece { part, length, radius_start, radius_end, turn_in }
// part: 0 przediglicowy | 1 iglica | 2 luk | 3 prosta krzyzownicowa
// radius 0 = prosta; rowne = luk; rozne = krzywa przejsciowa
// turn_in = kat nagiecia iglicy (beta); 0 = iglica styczna do opornicy
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: lista odcinków, nie jeden kształt
Odnoga to **lista odcinków z katalogu**, kładziona po kolei od `PR`. Nic w niej
nie jest dopasowywane: skos i długość katalogowa są tym, do czego wynik się
**sprawdza** (`angle_residual`, `length_residual`), a nie tym, co domyka ostatni
łuk. Typowe zestawy:
- **zwrotnica sieczna z iglicą prostą** — `[przediglicowy] [iglica prosta @ beta]
[łuk o alfa − beta] [prosta krzyżownicowa]`,
- **zwrotnica z iglicą łukową styczną do opornicy** — `[iglica łuk R od PR]
[łuk R] [prosta krzyżownicowa]`, `beta = 0` wszędzie: odnoga odchodzi stycznie,
bez załamania kierunku.
**Ścięte ostrze** (Koc, rys. 5.9). Iglicy nie da się zaostrzyć do zera. Łuk
iglicy jest **teoretycznie styczny do opornicy w PR**, ale sama iglica zaczyna
się dopiero tam, gdzie jest z czego — w *ostrzu*, w odległości `a` od PR — a jej
zestrugany dziób o długości `d` dochodzi do punktu **A**, gdzie iglica ma już
grubość `u`. To, co leży między PR a ostrzem, to odcinek przediglicowy: krzywa
już tam jest, pojazd jeszcze jedzie po torze zasadniczym.
Wszystko wychodzi z jednej drogi — do zadanego luzu między iglicą a opornicą:
```
s(y) = R * (acos(cos beta - y/R1) - beta) // iglica łukowa, R1 = R + s/2
s(y) = y / sin beta // iglica prosta
a + d = s(u) struganie = s(w) - a
```
Luz otwiera się na **łuku samej iglicy** (`R1 = R + s/2`, Koc 5.2), ale `s(y)`
jest stacją **po osi** — promień szyny zamienia luz na kąt, nie mierzy długości.
Rysunek wymiaruje iglicę po szynie, więc katalogowe `a + d` to `s(u)` przeskalowane
o `R1/R`; na tablicy 5.1 zgadza się do dziesiątej części milimetra przy
1200–1:18,5 i do sześciu przy 190–1:9. Dlatego typ niesie tylko `u`, `d` i `w`,
a `a` się **wyprowadza**.
(Templotowe `pl = w / tan beta` to to samo struganie rzutowane na opornicę.)
**Model:** zespół iglic kotwiony w ostrzu, zorientowany wzdłuż osi iglicy w tym
punkcie; edytor podaje `PR`, ostrze, koniec dzioba `A` i koniec strugania jako
znaczniki (`kind` 0, 1, 7, 8).
## 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` i `pre_blade_length`~~ — zrobione inaczej: `lay_turnout` kładzie
**listę odcinków** z typu, więc przediglicowy, iglica (prosta pod `beta` albo
łukowa styczna), łuk i prosta krzyżownicowa to po prostu jej pozycje,
- **wymiary Id-1 w szablonach** (`turnout_preset`): długości iglic i odcinków
przediglicowych, `u` per typ — dziś liczone z odsunięcia pięty,
- 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`.