Devlog #16 — Budowa Bridge Designer: MES w przeglądarce

Żadnej zewnętrznej biblioteki, żadnego WebAssembly, żadnego serwera — tylko JavaScript składający globalną macierz sztywności, rozwiązujący ją eliminacją Gaussa i renderujący naprężenia jako kolor w czasie rzeczywistym. Oto dokładnie, jak to działa.

Czym jest metoda elementów skończonych?

MES dzieli ciągłą strukturę na proste elementy. Dla kratownicy 2D (typ stosowany w mostach) każdy element to pręt, który może przenosić tylko obciążenie osiowe — rozciąganie lub ściskanie. Analiza znajduje, jak bardzo każdy pręt odkształca się pod wpływem obciążeń zewnętrznych, a następnie oblicza naprężenie wewnętrzne.

Dla kratownicy z $n$ węzłami układ równań ma postać: $\mathbf{K} \cdot \mathbf{u} = \mathbf{f}$, gdzie $\mathbf{K}$ to globalna macierz sztywności ($2n \times 2n$), $\mathbf{u}$ to wektor przemieszczeń, a $\mathbf{f}$ to wektor sił przyłożonych.

Krok 1: budowa macierzy sztywności elementu

Każdy pręt kratownicy wnosi lokalną macierz sztywności 4×4. Zależy ona od kąta orientacji pręta $\theta$, jego przekroju $A$, modułu Younga $E$ i długości $L$:

function elementStiffness(nodeA, nodeB, E, A) { const dx = nodeB.x - nodeA.x; const dy = nodeB.y - nodeA.y; const L = Math.hypot(dx, dy); const c = dx / L; // cos θ const s = dy / L; // sin θ const k = E * A / L; // Lokalna macierz 4×4 [ux_i, uy_i, ux_j, uy_j] return [ [ c*c, c*s, -c*c, -c*s], [ c*s, s*s, -c*s, -s*s], [-c*c, -c*s, c*c, c*s], [-c*s, -s*s, c*s, s*s], ].map(row => row.map(v => v * k)); }

Krok 2: składanie globalnej macierzy sztywności

Macierz 4×4 każdego elementu jest "wstemplowywana" we właściwe miejsca globalnej macierzy $2n \times 2n$. Węzeł $i$ zajmuje stopnie swobody $[2i, 2i+1]$ (x i y):

function assembleK(nodes, elements, E, A) { const n = nodes.length; const dof = n * 2; const K = Array.from({ length: dof }, () => new Float64Array(dof)); for (const el of elements) { const ke = elementStiffness(nodes[el.i], nodes[el.j], E, A); const idx = [el.i*2, el.i*2+1, el.j*2, el.j*2+1]; for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) K[idx[r]][idx[c]] += ke[r][c]; } return K; }

Krok 3: nakładanie warunków brzegowych

Węzły utwierdzone (podpory) mają znane przemieszczenie (zero). Wymuszamy to, zerując wiersz i kolumnę dla każdego utwierdzonego stopnia swobody i ustawiając przekątną na 1:

for (const dof of fixedDofs) { for (let i = 0; i < K.length; i++) { K[dof][i] = 0; K[i][dof] = 0; } K[dof][dof] = 1; f[dof] = 0; // brak przemieszczenia w podporze }

Krok 4: solver eliminacji Gaussa

Po nałożeniu warunków brzegowych rozwiązujemy $\mathbf{K} \cdot \mathbf{u} = \mathbf{f}$ eliminacją Gaussa z częściowym pivotingiem. Dla typowego mostu (50 węzłów) zajmuje to poniżej 1 ms w JavaScript:

function gaussianElimination(A, b) { const n = b.length; // Eliminacja w przód z częściowym pivotingiem for (let col = 0; col < n; col++) { // Znajdź wiersz z maksymalną wartością bezwzględną w kolumnie let maxRow = col; for (let r = col+1; r < n; r++) if (Math.abs(A[r][col]) > Math.abs(A[maxRow][col])) maxRow = r; [A[col], A[maxRow]] = [A[maxRow], A[col]]; [b[col], b[maxRow]] = [b[maxRow], b[col]]; for (let r = col+1; r < n; r++) { const factor = A[r][col] / A[col][col]; b[r] -= factor * b[col]; for (let c = col; c < n; c++) A[r][c] -= factor * A[col][c]; } } // Podstawianie wsteczne const x = new Float64Array(n); for (let i = n-1; i >= 0; i--) { x[i] = b[i]; for (let j = i+1; j < n; j++) x[i] -= A[i][j] * x[j]; x[i] /= A[i][i]; } return x; }

Krok 5: obliczanie naprężenia w pręcie

Znając przemieszczenie $\mathbf{u}$, odkształcenie osiowe w każdym pręcie wynosi: $\varepsilon = (du_x \cdot \cos\theta + du_y \cdot \sin\theta) / L$, a naprężenie $\sigma = E \cdot \varepsilon$. Wartość dodatnia = rozciąganie (pręt jest rozciągany), ujemna = ściskanie.

function memberStress(el, nodes, u, E) { const { i, j } = el; const dx = nodes[j].x - nodes[i].x; const dy = nodes[j].y - nodes[i].y; const L = Math.hypot(dx, dy); const c = dx / L, s = dy / L; const du = (u[j*2] - u[i*2]) * c + (u[j*2+1] - u[i*2+1]) * s; return E * du / L; // σ = E·ε }

Krok 6: kolorowanie naprężeń w Three.js

Wartość naprężenia każdego pręta jest mapowana na kolor: niebieski (maksymalne ściskanie) → biały (brak obciążenia) → czerwony (maksymalne rozciąganie).

0
+
ŚciskanieZeroRozciąganie
function stressToColor(sigma, maxSigma) { const t = clamp(sigma / maxSigma, -1, 1); if (t < 0) return new THREE.Color().lerpColors(white, blue, -t); else return new THREE.Color().lerpColors(white, red, t); }

Wizualizacja odkształcenia (skala 50×)

Rzeczywiste ugięcia stalowego mostu pod obciążeniem to milimetry — niewidoczne w normalnej skali. Stosujemy konfigurowalny współczynnik wzmocnienia (domyślnie 50×) do przemieszczeń węzłów, aby odkształcenie było widoczne bez zniekształcania geometrii. Pręt pozostaje połączony ze (przeskalowanymi) przemieszczonymi węzłami:

const DEFORM_SCALE = 50; nodes.forEach((node, i) => { node.displayX = node.x + u[i*2] * DEFORM_SCALE; node.displayY = node.y + u[i*2+1] * DEFORM_SCALE; });

Gotowe typy mostów

Kratownica Warrena
Naprzemienne elementy ukośne pod kątem 60°. Efektywne wykorzystanie materiału, równomierny rozkład naprężeń.
Najbardziej równomierny rozkład naprężeń
Kratownica Pratta
Elementy pionowe ściskane, elementy ukośne rozciągane. Preferowana dla długich przęseł.
Świetna przy dużych obciążeniach centralnych
Kratownica Howe'a
Odwrotność Pratta — elementy ukośne ściskane. Mniej efektywna dla stali, ale odpowiednia dla drewna.
Klasyczna forma z XIX wieku

Uwaga o dokładności: Nasza implementacja obsługuje wyłącznie elementy kratownicy 2D. Prawdziwe mosty wymagają elementów belkowych 3D (Eulera-Bernoulliego), aby uwzględnić momenty zginające. Dla celu edukacyjnego — demonstracji składania sztywności i rozwiązywania MES — analiza kratownicy 2D jest dokładna i szybka w przeglądarce.

Wypróbuj teraz

Otwórz Bridge Designer, wybierz gotowy typ, przyłóż obciążenia w różnych węzłach i obserwuj naprężenia kolorowane na żywo oraz aktualizujące się odkształcenie. Panel współczynnika bezpieczeństwa zmienia kolor na czerwony, gdy którykolwiek element przekracza granicę plastyczności.