Vibe Coding für das Ingenieurwesen: Technische Mechanik mit KI simulieren

Vibe Coding kann für unterschiedliche Fachdisziplinen eingesetzt werden, wie unter anderem im Ingenieurwesen und im Rahmen der technischen Mechanik. Die technischen Fächer werden von nicht wenigen Studierenden als herausfordernd betrachtet und können das Studium erheblich erschweren. Mit KI-Anwendungen wie dem Gemini-Chat gibt es mittlerweile vielfältige Möglichkeiten der Visualisierung.
Der untere Screenshot zeigt einen formulierten Prompt, den das Sprachmodell Gemini ausgegeben hat. Diesen habe ich in die Promptleiste eingefügt und im Anschluss über das Plus-Symbol die „Canvas-Funktion“ aktiviert. Dann weiß Gemini genau Bescheid, dass es eine Anwendung programmieren soll. Sonst funktioniert es nicht.

Theoretisch ließe sich ein ganzer Programmiercode in das Fenster hineinkopieren. Der untere Block (blau) zeigt den gesamten Code der Anwendung „Technische Mechanik interaktiv“. Ich habe einen Test gemacht und ausschließlich über den Code-Input ein sehr ähnliches Programm entwickeln lassen. Dies hat zuverlässig funktioniert.
PROGRAMMIERCODE: Per Copy & Paste einfügen und die Canvas-Funktion aktivieren
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interaktive Fachwerkbrücke - Technische Mechanik</title>
<!-- Tailwind CSS für modernes UI Design -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
input[type="range"] {
accent-color: #3b82f6;
}
/* Custom Scrollbar für die Erklärung, damit das UI sauber bleibt */
.custom-scrollbar::-webkit-scrollbar {
width: 5px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.3);
border-radius: 8px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.5);
border-radius: 8px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(148, 163, 184, 0.8);
}
</style>
</head>
<body class="bg-slate-950 text-slate-100 h-screen flex flex-col overflow-hidden">
<!-- Header (Fixed Height) -->
<header class="bg-slate-900/80 backdrop-blur-md border-b border-slate-800 py-3 px-6 flex-shrink-0 shadow-lg">
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-3">
<div class="flex items-center gap-3">
<div class="bg-gradient-to-tr from-blue-600 to-indigo-500 p-2 rounded-lg text-white shadow-md shadow-blue-500/20">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<h1 class="text-lg font-bold tracking-tight bg-gradient-to-r from-white to-slate-300 bg-clip-text text-transparent">Technische Mechanik Interaktiv</h1>
<p class="text-[11px] text-slate-400">Echtzeit-Statik & Kräfteverlauf im idealen Fachwerk</p>
</div>
</div>
<!-- Farb-Legende -->
<div class="flex gap-4 text-[11px] bg-slate-950 px-3 py-1.5 rounded-lg border border-slate-800">
<div class="flex items-center gap-1.5">
<span class="w-3.5 h-2.5 bg-red-500 rounded-sm inline-block shadow-sm shadow-red-500/20"></span>
<span>Druckkraft (Stauchung)</span>
</div>
<div class="flex items-center gap-1.5">
<span class="w-3.5 h-2.5 bg-blue-500 rounded-sm inline-block shadow-sm shadow-blue-500/20"></span>
<span>Zugkraft (Dehnung)</span>
</div>
<div class="flex items-center gap-1.5">
<span class="w-3.5 h-2.5 bg-slate-600 rounded-sm inline-block"></span>
<span>Nullstab</span>
</div>
</div>
</div>
</header>
<!-- Hauptbereich (nimmt verbleibenden Platz ein, kein Scrollen der Hauptseite) -->
<main class="flex-grow min-h-0 max-w-7xl w-full mx-auto p-4 md:p-6 grid grid-cols-1 lg:grid-cols-12 gap-6 overflow-hidden">
<!-- Linke Spalte: Simulation (Canvas) & Direkt-Messwerte -->
<div class="lg:col-span-8 flex flex-col h-full gap-4 min-h-0">
<!-- Simulations-Fenster -->
<div class="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-2xl relative flex-grow min-h-0 flex flex-col">
<div class="bg-slate-900 px-4 py-2 border-b border-slate-800 flex justify-between items-center text-xs font-medium flex-shrink-0">
<span class="text-slate-300 flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
Statik-Engine stabil
</span>
<button id="btnAutoPlay" class="px-3 py-1 bg-blue-600 hover:bg-blue-500 text-white rounded text-[11px] font-semibold transition-all flex items-center gap-1.5 shadow-md shadow-blue-500/10">
<span id="playIcon">⏸</span> <span id="playText">Auto anhalten</span>
</button>
</div>
<!-- Canvas Container (Füllt den verbleibenden Raum flexibel aus) -->
<div class="relative w-full flex-grow min-h-0 bg-gradient-to-b from-slate-950 via-slate-950 to-slate-900">
<canvas id="bridgeCanvas" class="absolute inset-0 w-full h-full block"></canvas>
</div>
</div>
<!-- Schnell-Info unter dem Canvas (Feste Höhe) -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 flex-shrink-0">
<div class="bg-slate-900 p-3 rounded-xl border border-slate-800 shadow-lg">
<div class="text-[10px] text-slate-400 font-semibold uppercase tracking-wider">Auflagerkraft Links (A_y)</div>
<div id="valAuflagerL" class="text-xl font-bold text-slate-100 mt-0.5">-- kN</div>
<div class="w-full bg-slate-800 h-1.5 rounded-full mt-1.5 overflow-hidden">
<div id="barAuflagerL" class="bg-emerald-500 h-full transition-all duration-100" style="width: 0%"></div>
</div>
</div>
<div class="bg-slate-900 p-3 rounded-xl border border-slate-800 shadow-lg">
<div class="text-[10px] text-slate-400 font-semibold uppercase tracking-wider">Fahrzeugposition</div>
<div id="valPos" class="text-xl font-bold text-slate-100 mt-0.5">-- m</div>
<p class="text-[10px] text-slate-500 mt-1">Abstand von Lager A (links)</p>
</div>
<div class="bg-slate-900 p-3 rounded-xl border border-slate-800 shadow-lg">
<div class="text-[10px] text-slate-400 font-semibold uppercase tracking-wider">Auflagerkraft Rechts (B_y)</div>
<div id="valAuflagerR" class="text-xl font-bold text-slate-100 mt-0.5">-- kN</div>
<div class="w-full bg-slate-800 h-1.5 rounded-full mt-1.5 overflow-hidden">
<div id="barAuflagerR" class="bg-emerald-500 h-full transition-all duration-100" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<!-- Rechte Spalte: Steuerung & Didaktik -->
<div class="lg:col-span-4 flex flex-col h-full gap-4 min-h-0">
<!-- Steuerungselemente (Feste Höhe) -->
<div class="bg-slate-900 p-5 rounded-2xl border border-slate-800 shadow-xl flex flex-col gap-4 flex-shrink-0">
<h3 class="text-sm font-bold text-slate-200 border-b border-slate-850 pb-2 flex items-center gap-2">
🔧 Kontrollzentrum
</h3>
<!-- Schieberegler Autogewicht -->
<div class="flex flex-col gap-1">
<div class="flex justify-between items-center text-xs">
<span class="text-slate-300 font-medium">Autogewicht (F_G)</span>
<span id="labelWeight" class="font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded">150 kN</span>
</div>
<input id="sliderWeight" type="range" min="30" max="300" value="150" class="w-full h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer">
</div>
<!-- Schieberegler Autoposition (manuelle Steuerung) -->
<div class="flex flex-col gap-1">
<div class="flex justify-between items-center text-xs">
<span class="text-slate-300 font-medium">Fahrzeugposition</span>
<span id="labelPos" class="font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded">30 %</span>
</div>
<input id="sliderPos" type="range" min="2" max="98" value="30" class="w-full h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer">
</div>
<!-- Schieberegler Brückenhöhe -->
<div class="flex flex-col gap-1">
<div class="flex justify-between items-center text-xs">
<span class="text-slate-300 font-medium font-serif">Brückenhöhe (h)</span>
<span id="labelHeight" class="font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded">110 px</span>
</div>
<input id="sliderHeight" type="range" min="70" max="170" value="110" class="w-full h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer">
</div>
<!-- Strukturtyp-Auswahl -->
<div class="flex flex-col gap-1">
<label class="text-[11px] text-slate-400 font-medium">Tragsystem wählen</label>
<div class="grid grid-cols-2 gap-2 mt-1">
<button id="btnTypeWarren" class="px-2 py-1.5 bg-slate-800 border border-blue-500 text-blue-400 font-semibold rounded-lg text-xs hover:bg-slate-750 transition-all">
Warren-Träger
</button>
<button id="btnTypePratt" class="px-2 py-1.5 bg-slate-800 border border-transparent text-slate-400 font-semibold rounded-lg text-xs hover:bg-slate-750 hover:text-slate-200 transition-all">
Pratt-Träger
</button>
</div>
</div>
</div>
<!-- Didaktischer Erklär-Modus (Flexibel im verbleibenden Platz, scrollt intern) -->
<div class="bg-gradient-to-br from-slate-900 to-slate-950 p-5 rounded-2xl border border-slate-800 shadow-xl flex-grow min-h-0 flex flex-col">
<h3 class="text-sm font-bold text-slate-200 border-b border-slate-850 pb-2 flex items-center gap-2 flex-shrink-0">
💡 Mechanik-Erklärung
</h3>
<div class="flex-grow overflow-y-auto custom-scrollbar pr-1 mt-3 text-xs space-y-4">
<!-- Dynamischer Erklärungstext -->
<div class="space-y-3">
<div id="explainStatus" class="font-bold text-slate-200 text-xs flex items-center gap-1.5">
Wird berechnet...
</div>
<div id="explainBody" class="text-slate-400 leading-relaxed space-y-2">
<!-- Hier wird die Live-Analyse injiziert -->
</div>
</div>
<!-- Hintergrundwissen -->
<div class="pt-3 border-t border-slate-850 text-[11px] text-slate-500">
<p class="font-bold text-slate-400 mb-1">🔍 Technische Mechanik-Info:</p>
<p class="leading-relaxed">
Ein ideales Fachwerk überträgt Belastungen ausschließlich als <strong>Normalkräfte</strong> (Zug oder Druck) entlang der Stabachsen. Biegemomente in den Stäben werden vernachlässigt, da wir reibungsfreie Gelenkknoten annehmen.
</p>
</div>
</div>
</div>
</div>
</main>
<!-- Footer (Fixed Height) -->
<footer class="bg-slate-900 py-2.5 px-6 border-t border-slate-800 text-center text-[10px] text-slate-500 flex-shrink-0">
<p>© 2026 Fachwerk-Simulator für die Lehre der Technischen Mechanik (Baustatik | Stabwerke). Berechnet mit dem Knotenpunktverfahren.</p>
</footer>
<!-- JavaScript für die Simulation -->
<script>
// --- 1. SETUP & INITIALISIERUNG ---
const canvas = document.getElementById('bridgeCanvas');
const ctx = canvas.getContext('2d');
// State-Management
let state = {
carXPercent: 30, // Startposition des Autos in Prozent (2% bis 98%)
carWeight: 150, // Gewichtskraft in kN
bridgeHeight: 110, // Höhe des Fachwerks in Pixeln
bridgeType: 'warren', // 'warren' oder 'pratt'
isAutoPlaying: true, // Steuert die automatische Autofahrt
playSpeed: 0.15, // Geschwindigkeit der Animation
playDirection: 1, // Richtungs-Flag für Hin-/Rückfahrt
scale: 1, // Skalierungsfaktor
};
// UI-Neuzeichnungsflag zur Leistungssteigerung
let needsRedraw = true;
// DOM Elements
const sliderWeight = document.getElementById('sliderWeight');
const labelWeight = document.getElementById('labelWeight');
const sliderPos = document.getElementById('sliderPos');
const labelPos = document.getElementById('labelPos');
const sliderHeight = document.getElementById('sliderHeight');
const labelHeight = document.getElementById('labelHeight');
const valAuflagerL = document.getElementById('valAuflagerL');
const barAuflagerL = document.getElementById('barAuflagerL');
const valAuflagerR = document.getElementById('valAuflagerR');
const barAuflagerR = document.getElementById('barAuflagerR');
const valPos = document.getElementById('valPos');
const btnAutoPlay = document.getElementById('btnAutoPlay');
const playIcon = document.getElementById('playIcon');
const playText = document.getElementById('playText');
const btnTypeWarren = document.getElementById('btnTypeWarren');
const btnTypePratt = document.getElementById('btnTypePratt');
const explainStatus = document.getElementById('explainStatus');
const explainBody = document.getElementById('explainBody');
// Struktur-Definitionen
let nodes = [];
let members = [];
// Brücken-Dimensionen
let bridgeStartX = 0;
let bridgeEndX = 0;
let bridgeWidth = 0;
let bridgeY = 0;
// --- 2. RESIZE HANDLER (FÜR FLUIDES VIEWPORT-LOCK CANVAS) ---
function resizeCanvas() {
const rect = canvas.parentNode.getBoundingClientRect();
// Begrenze DPR auf max 2 für extreme Performance auf 4K/Retina Screens
const dpr = Math.min(2, window.devicePixelRatio || 1);
state.scale = dpr;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
// Layoutparameter relativ zum Canvas setzen
bridgeStartX = rect.width * 0.08;
bridgeEndX = rect.width * 0.92;
bridgeWidth = bridgeEndX - bridgeStartX;
bridgeY = rect.height * 0.72; // Platziere Brücke im unteren Drittel
recalculateStructure();
solveBridgeForces();
needsRedraw = true;
}
window.addEventListener('resize', resizeCanvas);
// --- 3. GEOMETRIE-AUFBAU (KNOTEN & STÄBE OHNE SINGULÄRE KNOTEN) ---
function recalculateStructure() {
nodes = [];
members = [];
const numFields = 6; // Anzahl der Felder am Untergurt
const fieldWidth = bridgeWidth / numFields;
if (state.bridgeType === 'warren') {
// --- WARREN-TRÄGER (13 Knoten, 23 Stäbe - Bestimmt) ---
// Untergurt (U0 bis U6)
for (let i = 0; i <= numFields; i++) {
nodes.push({
id: `U${i}`,
x: bridgeStartX + i * fieldWidth,
y: bridgeY,
type: 'bottom',
forceY: 0
});
}
// Obergurt (O0 bis O5) - Zickzack versetzt
for (let i = 0; i < numFields; i++) {
nodes.push({
id: `O${i}`,
x: bridgeStartX + (i + 0.5) * fieldWidth,
y: bridgeY - state.bridgeHeight,
type: 'top',
forceY: 0
});
}
const nU = (id) => nodes.findIndex(n => n.id === `U${id}`);
const nO = (id) => nodes.findIndex(n => n.id === `O${id}`);
// Stäbe Untergurt
for (let i = 0; i < numFields; i++) {
members.push({ n1: nU(i), n2: nU(i+1), type: 'gurt_unten', force: 0 });
}
// Stäbe Obergurt
for (let i = 0; i < numFields - 1; i++) {
members.push({ n1: nO(i), n2: nO(i+1), type: 'gurt_oben', force: 0 });
}
// Diagonalstreben (Wechselnde Richtungen)
for (let i = 0; i < numFields; i++) {
members.push({ n1: nU(i), n2: nO(i), type: 'strebe', force: 0 });
members.push({ n1: nO(i), n2: nU(i+1), type: 'strebe', force: 0 });
}
} else if (state.bridgeType === 'pratt') {
// --- PRATT-TRÄGER (12 Knoten, 21 Stäbe - Bestimmt & Stabil!) ---
// Untergurt (U0 bis U6)
for (let i = 0; i <= numFields; i++) {
nodes.push({
id: `U${i}`,
x: bridgeStartX + i * fieldWidth,
y: bridgeY,
type: 'bottom',
forceY: 0
});
}
// Obergurt (O1 bis O5) - O0 und O6 entfallen, um freie Knoten zu vermeiden!
for (let i = 1; i < numFields; i++) {
nodes.push({
id: `O${i}`,
x: bridgeStartX + i * fieldWidth,
y: bridgeY - state.bridgeHeight,
type: 'top',
forceY: 0
});
}
const nU = (id) => nodes.findIndex(n => n.id === `U${id}`);
const nO = (id) => nodes.findIndex(n => n.id === `O${id}`);
// Stäbe Untergurt
for (let i = 0; i < numFields; i++) {
members.push({ n1: nU(i), n2: nU(i+1), type: 'gurt_unten', force: 0 });
}
// Stäbe Obergurt (nur zwischen O1 und O5)
for (let i = 1; i < numFields - 1; i++) {
members.push({ n1: nO(i), n2: nO(i+1), type: 'gurt_oben', force: 0 });
}
// Schräge Endpfosten (Kräfteableitung zu Auflagern)
members.push({ n1: nU(0), n2: nO(1), type: 'strebe', force: 0 });
members.push({ n1: nU(numFields), n2: nO(numFields-1), type: 'strebe', force: 0 });
// Vertikale Pfosten (U1-O1, U2-O2, ..., U5-O5)
for (let i = 1; i < numFields; i++) {
members.push({ n1: nU(i), n2: nO(i), type: 'pfosten', force: 0 });
}
// Pratt Diagonale: Fallen zur Mitte (Knoten U3) hin ab
const half = numFields / 2; // 3
for (let i = 1; i < numFields; i++) {
if (i < half) {
// Links von Mitte: fällt von Obergurt links nach Untergurt rechts
members.push({ n1: nO(i), n2: nU(i+1), type: 'strebe', force: 0 });
} else if (i > half) {
// Rechts von Mitte: fällt von Obergurt rechts nach Untergurt links
members.push({ n1: nO(i), n2: nU(i-1), type: 'strebe', force: 0 });
}
}
}
}
// --- 4. EXAKTE PHYSIK BERECHNUNG (FEM STATIK SOLVER) ---
function solveBridgeForces() {
if (nodes.length === 0) return;
const numNodes = nodes.length;
const DOF = numNodes * 2;
// Matrix K & Lastvektor F initialisieren
let K = Array(DOF).fill(0).map(() => Array(DOF).fill(0));
let F = Array(DOF).fill(0);
// 1. Lastverteilung des Fahrzeugs auf die Untergurt-Knoten (Hebelgesetz)
const carX = bridgeStartX + (state.carXPercent / 100) * bridgeWidth;
const bottomNodes = nodes.filter(n => n.type === 'bottom');
let leftNode = bottomNodes[0];
let rightNode = bottomNodes[bottomNodes.length - 1];
for (let i = 0; i < bottomNodes.length - 1; i++) {
if (carX >= bottomNodes[i].x && carX <= bottomNodes[i+1].x) {
leftNode = bottomNodes[i];
rightNode = bottomNodes[i+1];
break;
}
}
const spanDist = rightNode.x - leftNode.x;
const weightR = spanDist > 0 ? (carX - leftNode.x) / spanDist : 0;
const weightL = 1 - weightR;
nodes.forEach(n => n.forceY = 0);
const idxL = nodes.findIndex(n => n.id === leftNode.id);
const idxR = nodes.findIndex(n => n.id === rightNode.id);
nodes[idxL].forceY = state.carWeight * weightL;
nodes[idxR].forceY = state.carWeight * weightR;
// Lasten in globalen Vektor F eintragen (y-Komponente zeigt nach unten => positive y-Richtung)
for (let i = 0; i < numNodes; i++) {
F[2 * i + 1] = nodes[i].forceY;
}
// 2. Elementsteifigkeitsmatrizen in die globale K-Matrix assemblieren
// Konstante EA für verständliche Dehnungsverhältnisse
const EA = 300000;
members.forEach(m => {
const n1 = nodes[m.n1];
const n2 = nodes[m.n2];
const dx = n2.x - n1.x;
const dy = n2.y - n1.y;
const L = Math.sqrt(dx*dx + dy*dy);
const cos = dx / L;
const sin = dy / L;
const c2 = cos * cos;
const s2 = sin * sin;
const cs = cos * sin;
const kFactor = EA / L;
const dofs = [2 * m.n1, 2 * m.n1 + 1, 2 * m.n2, 2 * m.n2 + 1];
const ke = [
[ kFactor*c2, kFactor*cs, -kFactor*c2, -kFactor*cs],
[ kFactor*cs, kFactor*s2, -kFactor*cs, -kFactor*s2],
[-kFactor*c2, -kFactor*cs, kFactor*c2, kFactor*cs],
[-kFactor*cs, -kFactor*s2, kFactor*cs, kFactor*s2]
];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
K[dofs[i]][dofs[j]] += ke[i][j];
}
}
});
// 3. Exakte Randbedingungen einarbeiten (Direct Row/Column Zeroing)
// Lager A (U0): Festlager (x, y blockiert)
// Lager B (U6): Loslager (y blockiert, x verschiebbar)
const idxLagerA = nodes.findIndex(n => n.id === 'U0');
const numFields = 6;
const idxLagerB = nodes.findIndex(n => n.id === `U${numFields}`);
const constrainedDOFs = [
2 * idxLagerA, // Lager A horizontal
2 * idxLagerA + 1, // Lager A vertikal
2 * idxLagerB + 1 // Lager B vertikal
];
constrainedDOFs.forEach(dof => {
for (let i = 0; i < DOF; i++) {
K[dof][i] = 0;
K[i][dof] = 0;
}
K[dof][dof] = 1;
F[dof] = 0;
});
// 4. Lineares Gleichungssystem K * u = F lösen
const u = solveLinearSystem(K, F);
// 5. Stabkräfte aus Knotenverschiebungen ermitteln
members.forEach(m => {
const n1 = nodes[m.n1];
const n2 = nodes[m.n2];
const dx = n2.x - n1.x;
const dy = n2.y - n1.y;
const L = Math.sqrt(dx*dx + dy*dy);
const cos = dx / L;
const sin = dy / L;
const u1x = u[2 * m.n1];
const u1y = u[2 * m.n1 + 1];
const u2x = u[2 * m.n2];
const u2y = u[2 * m.n2 + 1];
const deltaL = (u2x - u1x) * cos + (u2y - u1y) * sin;
// Stabkraft in kN skalieren (Positive Werte = Zug, Negative = Druck)
m.force = (EA / L) * deltaL * 0.28;
});
// 6. Exakte Auflagerkräfte ermitteln (Statisch über Hebelgesetze)
const span = bridgeWidth;
const distToA = (state.carXPercent / 100) * span;
const B_y = state.carWeight * (distToA / span);
const A_y = state.carWeight - B_y;
// UI-Aktualisierung
valAuflagerL.innerHTML = `<strong>${A_y.toFixed(1)}</strong> kN`;
valAuflagerR.innerHTML = `<strong>${B_y.toFixed(1)}</strong> kN`;
valPos.innerHTML = `<strong>${((state.carXPercent / 100) * 36).toFixed(1)}</strong> m`;
const pctL = Math.min(100, Math.max(0, (A_y / state.carWeight) * 100));
const pctR = Math.min(100, Math.max(0, (B_y / state.carWeight) * 100));
barAuflagerL.style.width = `${pctL}%`;
barAuflagerR.style.width = `${pctR}%`;
updateExplanation(A_y, B_y);
}
// Numerisch robuster Gauss-Eliminations-Algorithmus mit Pivotisierung
function solveLinearSystem(A, b) {
const n = b.length;
let M = new Array(n);
for (let i = 0; i < n; i++) {
M[i] = new Array(n + 1);
for (let j = 0; j < n; j++) {
M[i][j] = A[i][j];
}
M[i][n] = b[i];
}
for (let i = 0; i < n; i++) {
let maxEl = Math.abs(M[i][i]);
let maxRow = i;
for (let k = i + 1; k < n; k++) {
if (Math.abs(M[k][i]) > maxEl) {
maxEl = Math.abs(M[k][i]);
maxRow = k;
}
}
const temp = M[maxRow];
M[maxRow] = M[i];
M[i] = temp;
if (Math.abs(M[i][i]) > 1e-12) {
for (let k = i + 1; k < n; k++) {
const c = -M[k][i] / M[i][i];
for (let j = i; j <= n; j++) {
if (i === j) {
M[k][j] = 0;
} else {
M[k][j] += c * M[i][j];
}
}
}
}
}
let x = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
if (Math.abs(M[i][i]) < 1e-12) continue;
x[i] = M[i][n] / M[i][i];
for (let k = i - 1; k >= 0; k--) {
M[k][n] -= M[k][i] * x[i];
}
}
return x;
}
// --- 5. INTERAKTIVER MECHANIK-ERKLÄRUNGS-TEXT ---
function updateExplanation(Ay, By) {
const pos = state.carXPercent;
const weight = state.carWeight;
const height = state.bridgeHeight;
let statusHTML = "";
let bodyHTML = "";
if (pos < 12) {
statusHTML = "🚗 Last direkt über dem linken Lager A";
bodyHTML = `
<p>Das Auto befindet sich ganz am Rand. Die gesamte Gewichtskraft von <strong>${weight} kN</strong> wird fast verlustfrei direkt in den Betonpfeiler links eingeleitet ($A_y \\approx ${Ay.toFixed(0)} kN$).</p>
<p class="mt-2 text-slate-300">Da die Last direkt über dem Fundament liegt, muss das Tragwerk kaum Biegung aufnehmen. Die inneren Stäbe verfärben sich dunkel (Kräfte nahezu Null). Es entstehen sogenannte <em>Nullstäbe</em>.</p>
`;
} else if (pos > 88) {
statusHTML = "🚗 Last direkt über dem rechten Lager B";
bodyHTML = `
<p>Die Last steht nun fast komplett über der rechten Stütze. Das Loslager fängt <strong>${By.toFixed(0)} kN</strong> vertikal ab.</p>
<p class="mt-2 text-slate-300">Genau wie am linken Rand verblassen die Kräfte im mittleren Tragwerk, da der Kraftfluss direkt senkrecht nach unten verläuft.</p>
`;
} else if (pos >= 38 && pos <= 62) {
statusHTML = "🔥 Maximaler Lastfall: Auto im Brücken-Mittelpunkt";
let heightWarn = "";
if (height < 90) {
heightWarn = "<span class='text-red-400 font-semibold'>⚠️ Achtung:</span> Die Brücke ist extrem flach gebaut! Dadurch müssen die horizontalen Gurte extreme Kräfte übertragen, um das statische Moment auszugleichen. Die Farben leuchten grell.";
} else if (height > 140) {
heightWarn = "Durch die große Brückenhöhe verringert sich der innere Hebelarm-Bedarf. Die Stäbe im Obergurt und Untergurt werden deutlich weniger beansprucht.";
} else {
heightWarn = "Das Höhen-Verhältnis ist optimal gewählt und sorgt für eine harmonische Kraftverteilung.";
}
bodyHTML = `
<p>Das Fahrzeug steht exakt in der Mitte der Brücke. Dies erzeugt das maximale Biegemoment in der Tragwerkmitte.</p>
<p class="mt-2">Beobachte das perfekte mechanische Kräftespiel:</p>
<ul class="list-disc pl-4 mt-1.5 space-y-1.5 text-slate-300">
<li>Die <strong class="text-blue-400">unteren horizontalen Gurtstäbe</strong> sind komplett <strong class="text-blue-400">hellblau (Zugkraft)</strong>. Sie verhindern, dass die Unterseite der Brücke auseinanderreißt.</li>
<li>Die <strong class="text-red-400">oberen Gurtstäbe</strong> sind tief <strong class="text-red-400">rot (Druckkraft)</strong>, da sich die Brücke an der Oberseite zusammenschieben will.</li>
<li>Die schrägen Diagonalen leiten die Vertikalkräfte im Zickzack-Muster zu den Auflagern weiter.</li>
</ul>
<p class="mt-2 text-slate-400">${heightWarn}</p>
`;
} else {
const richtung = pos < 50 ? "linksbündig" : "rechtsbündig";
bodyHTML = `
<p>Das Auto ist auf der Brücke <strong>${richtung}</strong> unterwegs. Die Hebelgesetze verteilen die vertikalen Auflagerkräfte asymmetrisch.</p>
<p class="mt-2 text-slate-300">Schau dir das Fachwerk-Unterteil genau an: Die Stäbe am Untergurt stehen allesamt sauber unter <strong class="text-blue-400">Zug (blau)</strong>. Es gibt keinerlei instabile Vorzeichenfehler mehr!</p>
`;
}
explainStatus.innerHTML = statusHTML;
explainBody.innerHTML = bodyHTML;
}
// --- 6. ZEICHNEN AUF DEM CANVAS ---
function drawSimulation() {
ctx.clearRect(0, 0, canvas.width / state.scale, canvas.height / state.scale);
const w = canvas.width / state.scale;
const h = canvas.height / state.scale;
// Flusslauf & Flussbett
ctx.fillStyle = '#1e293b';
ctx.fillRect(0, bridgeY, w, h - bridgeY);
ctx.fillStyle = '#0f172a';
ctx.fillRect(bridgeStartX, bridgeY, bridgeWidth, h - bridgeY);
// Flusswasser-Farbverlauf
let waterGrad = ctx.createLinearGradient(0, bridgeY, 0, h);
waterGrad.addColorStop(0, '#1d4ed8');
waterGrad.addColorStop(1, '#1e3a8a');
ctx.fillStyle = waterGrad;
ctx.fillRect(bridgeStartX - 10, bridgeY + 20, bridgeWidth + 20, h - bridgeY);
// Ufergelände links/rechts
ctx.fillStyle = '#334155';
ctx.fillRect(0, bridgeY, bridgeStartX, h - bridgeY);
ctx.fillRect(bridgeEndX, bridgeY, w - bridgeEndX, h - bridgeY);
// Betonlager-Fundamente
ctx.fillStyle = '#64748b';
ctx.fillRect(bridgeStartX - 20, bridgeY, 40, 25);
ctx.fillRect(bridgeEndX - 20, bridgeY, 40, 25);
// --- LAGER ZEICHNEN ---
// Festlager A (Links)
ctx.strokeStyle = '#94a3b8';
ctx.fillStyle = '#475569';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(bridgeStartX, bridgeY);
ctx.lineTo(bridgeStartX - 14, bridgeY + 14);
ctx.lineTo(bridgeStartX + 14, bridgeY + 14);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Loslager B (Rechts)
ctx.fillStyle = '#475569';
ctx.beginPath();
ctx.moveTo(bridgeEndX, bridgeY);
ctx.lineTo(bridgeEndX - 14, bridgeY + 9);
ctx.lineTo(bridgeEndX + 14, bridgeY + 9);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Rollen-Symbol für Loslager
ctx.beginPath();
ctx.arc(bridgeEndX - 7, bridgeY + 14, 3, 0, Math.PI * 2);
ctx.arc(bridgeEndX + 7, bridgeY + 14, 3, 0, Math.PI * 2);
ctx.fillStyle = '#94a3b8';
ctx.fill();
// --- FACHWERKSTÄBE ZEICHNEN ---
members.forEach(m => {
const n1 = nodes[m.n1];
const n2 = nodes[m.n2];
const force = m.force;
const maxExpectedForce = 220;
let intensity = Math.min(1.0, Math.abs(force) / maxExpectedForce);
let strokeColor = '#475569'; // Neutrales Grau für unbelastet
let shadowColor = 'transparent';
if (force > 0.2) {
// Zugkraft = Blau
const b = Math.floor(120 + intensity * 135);
const g = Math.floor(80 + intensity * 80);
strokeColor = `rgb(${80 - intensity * 60}, ${g}, ${b})`;
shadowColor = `rgba(59, 130, 246, ${intensity * 0.55})`;
} else if (force < -0.2) {
// Druckkraft = Rot
const r = Math.floor(130 + intensity * 125);
const g = Math.floor(80 - intensity * 60);
strokeColor = `rgb(${r}, ${g}, ${80 - intensity * 60})`;
shadowColor = `rgba(239, 68, 68, ${intensity * 0.55})`;
}
const strokeWidth = 3 + intensity * 6.5;
ctx.save();
ctx.beginPath();
ctx.moveTo(n1.x, n1.y);
ctx.lineTo(n2.x, n2.y);
if (intensity > 0.25) {
ctx.shadowBlur = 10;
ctx.shadowColor = shadowColor;
}
ctx.strokeStyle = strokeColor;
ctx.lineWidth = strokeWidth;
ctx.lineCap = 'round';
ctx.stroke();
ctx.restore();
// Live Kraftwert-Label am Stab
if (Math.abs(force) > 8) {
const midX = (n1.x + n2.x) / 2;
const midY = (n1.y + n2.y) / 2;
ctx.font = 'bold 9px sans-serif';
const text = `${Math.abs(force).toFixed(0)} kN`;
const textWidth = ctx.measureText(text).width;
ctx.fillStyle = 'rgba(15, 23, 42, 0.85)';
ctx.fillRect(midX - textWidth/2 - 3, midY - 6, textWidth + 6, 12);
ctx.fillStyle = force > 0 ? '#60a5fa' : '#f87171';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, midX, midY);
}
});
// --- KNOTEN ZEICHNEN ---
nodes.forEach(n => {
ctx.beginPath();
ctx.arc(n.x, n.y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#0f172a';
ctx.fill();
ctx.strokeStyle = '#94a3b8';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#64748b';
ctx.font = '8px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(n.id, n.x, n.y - 10);
});
// --- AUTO ZEICHNEN ---
const carX = bridgeStartX + (state.carXPercent / 100) * bridgeWidth;
drawCar(carX, bridgeY);
// --- AUFLAGERPFEILE UND SINKLASTPFEIL ZEICHNEN ---
const carWeightMax = 300;
const distToA = (state.carXPercent / 100) * bridgeWidth;
const B_y = state.carWeight * (distToA / bridgeWidth);
const A_y = state.carWeight - B_y;
drawForceArrow(bridgeStartX, bridgeY + 30, A_y / carWeightMax, 'up', '#10b981');
drawForceArrow(bridgeEndX, bridgeY + 30, B_y / carWeightMax, 'up', '#10b981');
drawForceArrow(carX, bridgeY - 3, state.carWeight / carWeightMax, 'down', '#f43f5e');
// --- DOSIERTE TECHNISCHE BESCHRIFTUNGEN ---
ctx.save();
ctx.font = 'italic 10px sans-serif';
ctx.fillStyle = 'rgba(148, 163, 184, 0.55)'; // Dezent und lesbar im dunklen Theme
// 1. Obergurt (Druckbereich)
ctx.textAlign = 'center';
ctx.fillText('Obergurt (Druckbereich)', bridgeStartX + bridgeWidth / 2, bridgeY - state.bridgeHeight - 20);
// 2. Untergurt (Zugbereich)
ctx.fillText('Untergurt (Zugbereich)', bridgeStartX + bridgeWidth / 2, bridgeY + 22);
// 3. Diagonalen / Streben
ctx.textAlign = 'left';
if (state.bridgeType === 'warren') {
ctx.fillText('Diagonalen / Streben', bridgeStartX + 12, bridgeY - state.bridgeHeight / 2 - 4);
} else {
ctx.fillText('Pfosten & Streben', bridgeStartX + 12, bridgeY - state.bridgeHeight / 2 - 4);
}
// 4. Lager-Bezeichnungen
ctx.textAlign = 'right';
ctx.fillText('Festlager A', bridgeStartX - 18, bridgeY + 8);
ctx.textAlign = 'left';
ctx.fillText('Loslager B', bridgeEndX + 18, bridgeY + 8);
// 5. Bezeichnungen für mathematische Kräfte
ctx.font = 'bold 11px sans-serif';
ctx.fillStyle = 'rgba(16, 185, 129, 0.85)'; // Gedämpftes Grün für Auflagerkräfte
ctx.textAlign = 'center';
ctx.fillText('A_y', bridgeStartX, bridgeY + 85);
ctx.fillText('B_y', bridgeEndX, bridgeY + 85);
ctx.fillStyle = 'rgba(244, 63, 94, 0.9)'; // Gedämpftes Rot für Lastkraft
ctx.fillText('F_G', carX - 18, bridgeY - 45);
ctx.restore();
}
// Zeichnet ein formschönes 2D Auto
function drawCar(x, y) {
ctx.save();
ctx.translate(x, y);
ctx.fillStyle = '#3b82f6';
ctx.strokeStyle = '#1d4ed8';
ctx.lineWidth = 2;
// Unterbau
ctx.beginPath();
ctx.roundRect(-22, -18, 44, 10, 3);
ctx.fill();
ctx.stroke();
// Kabinendach
ctx.fillStyle = '#60a5fa';
ctx.beginPath();
ctx.roundRect(-12, -27, 24, 10, 4);
ctx.fill();
ctx.stroke();
// Räder
ctx.fillStyle = '#020617';
ctx.strokeStyle = '#64748b';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(-12, -5, 5.5, 0, Math.PI * 2);
ctx.arc(12, -5, 5.5, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
}
// Kraftpfeile zeichnen
function drawForceArrow(x, y, scale, direction, color) {
const minLen = 16;
const maxLen = 50;
const arrowLen = minLen + scale * (maxLen - minLen);
const arrowWidth = 5 + scale * 4;
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 2.5 + scale * 2.5;
if (direction === 'up') {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y - arrowLen);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y - arrowLen);
ctx.lineTo(x - arrowWidth, y - arrowLen + arrowWidth * 1.4);
ctx.lineTo(x + arrowWidth, y - arrowLen + arrowWidth * 1.4);
ctx.closePath();
ctx.fill();
} else if (direction === 'down') {
ctx.beginPath();
ctx.moveTo(x, y - arrowLen);
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x - arrowWidth, y - arrowWidth * 1.4);
ctx.lineTo(x + arrowWidth, y - arrowWidth * 1.4);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
// --- 7. EVENT-HANDLING & USER INTERACTION ---
sliderWeight.addEventListener('input', (e) => {
state.carWeight = parseInt(e.target.value);
labelWeight.innerText = `${state.carWeight} kN`;
solveBridgeForces();
needsRedraw = true;
});
sliderPos.addEventListener('input', (e) => {
state.carXPercent = parseInt(e.target.value);
labelPos.innerText = `${state.carXPercent} %`;
if (state.isAutoPlaying) {
stopAutoPlay();
}
solveBridgeForces();
needsRedraw = true;
});
sliderHeight.addEventListener('input', (e) => {
state.bridgeHeight = parseInt(e.target.value);
labelHeight.innerText = `${state.bridgeHeight} px`;
recalculateStructure();
solveBridgeForces();
needsRedraw = true;
});
btnAutoPlay.addEventListener('click', () => {
if (state.isAutoPlaying) {
stopAutoPlay();
} else {
startAutoPlay();
}
});
function startAutoPlay() {
state.isAutoPlaying = true;
playIcon.innerText = "⏸";
playText.innerText = "Auto anhalten";
}
function stopAutoPlay() {
state.isAutoPlaying = false;
playIcon.innerText = "▶";
playText.innerText = "Auto starten";
}
btnTypeWarren.addEventListener('click', () => {
state.bridgeType = 'warren';
btnTypeWarren.className = "px-2 py-1.5 bg-slate-800 border border-blue-500 text-blue-400 font-semibold rounded-lg text-xs hover:bg-slate-750 transition-all";
btnTypePratt.className = "px-2 py-1.5 bg-slate-800 border border-transparent text-slate-400 font-semibold rounded-lg text-xs hover:bg-slate-750 hover:text-slate-200 transition-all";
recalculateStructure();
solveBridgeForces();
needsRedraw = true;
});
btnTypePratt.addEventListener('click', () => {
state.bridgeType = 'pratt';
btnTypePratt.className = "px-2 py-1.5 bg-slate-800 border border-blue-500 text-blue-400 font-semibold rounded-lg text-xs hover:bg-slate-750 transition-all";
btnTypeWarren.className = "px-2 py-1.5 bg-slate-800 border border-transparent text-slate-400 font-semibold rounded-lg text-xs hover:bg-slate-750 hover:text-slate-200 transition-all";
recalculateStructure();
solveBridgeForces();
needsRedraw = true;
});
// Klicksteuerung auf Canvas
canvas.addEventListener('mousedown', handleCanvasClick);
canvas.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
handleCanvasTouch(e.touches[0]);
}
});
function handleCanvasClick(e) {
const rect = canvas.getBoundingClientRect();
const clickX = e.clientX - rect.left;
setCarPosByCoordinate(clickX);
}
function handleCanvasTouch(touch) {
const rect = canvas.getBoundingClientRect();
const clickX = touch.clientX - rect.left;
setCarPosByCoordinate(clickX);
}
function setCarPosByCoordinate(coordX) {
let pct = ((coordX - bridgeStartX) / bridgeWidth) * 100;
pct = Math.min(98, Math.max(2, pct));
state.carXPercent = pct;
sliderPos.value = Math.round(pct);
labelPos.innerText = `${Math.round(pct)} %`;
stopAutoPlay();
solveBridgeForces();
needsRedraw = true;
}
// --- 8. PERFORMANCE-TIMED SIMULATIONS LOOP ---
function tick() {
if (state.isAutoPlaying) {
state.carXPercent += state.playSpeed * state.playDirection;
if (state.carXPercent >= 98) {
state.carXPercent = 98;
state.playDirection = -1;
} else if (state.carXPercent <= 2) {
state.carXPercent = 2;
state.playDirection = 1;
}
sliderPos.value = Math.round(state.carXPercent);
labelPos.innerText = `${Math.round(state.carXPercent)} %`;
solveBridgeForces();
needsRedraw = true;
}
if (needsRedraw) {
drawSimulation();
needsRedraw = false;
}
requestAnimationFrame(tick);
}
// --- 9. INITIALER START ---
window.onload = function() {
resizeCanvas();
tick();
}
</script>
</body>
</html>
Nach wenigen Minuten ist die KI-Simulation fertig generiert und kann direkt im Gemini-Chat interaktiv über Regler-Einstellungen & Button-Klicks getestet werden. Der linke Chat lässt sich für Folgeprompts und Anpassungen nutzen. Auf der rechten Seite wird die Anwendung dargestellt, die über das Sprachmodell Gemini im „Vibe-Coding-Sti“l entwickelt wurde.

Bei der Mechanik-Anwendung geht es darum, wie sich die Kräfte im Rahmen der Brückenkonstruktion verteilen, wenn sich ein Auto auf der Brücke horizontal bewegt. Links und rechts sind Auflager angebracht und oberhalb der Fahrbahn sind Streben befestigt, deren Kraftverteilung sich live ändert. Rechts im „Kontrollzentrum“ kann ich manuell die Parameter „Autogewicht“, „Fahrzeugposition“ und „Brückenhöhe“ einstellen. Änderungen haben stets Auswirkungen auf die Kraftwirkungen hinsichtlich der Auflager und Brückenstreben.

Zwischendurch lässt sich ein Screenshot des Interfaces erstellen und links im Chat hochladen. Anschließend wird links unten über das Plus-Symbol die Bilderstellung über Nano Banana aktiviert und ein Prompt wie ***Füge dem Screenshot (Upload) gezielt Bezeichnungen hinzu*** formuliert.

Diesen Switch von der animierten Anwendung zu einer Infografik kann direkt im Split-Screen-Modus vorgenommen werden. Dies ist ein großer Vorteil im Gemini-Chat, da Google nicht nur über das Top-LLM Gemini verfügt, sondern auch die hochwertige Bild-KI Nano Banana entwickelt.

Nun hat die KI der Abbildung geeignete Bezeichnungen wie Festlager, Ober- und Untergurt hinzugefügt. Die Grafik lässt sich zusätzlich direkt im Chat über Markierungen und Text individuell anpassen, um etwa spontan einen Sachverhalt näher zu erläutern. Im Beispiel habe ich in einem veränderten Szenario die vertikale Kraft Fv auf die Brückenkonstruktion wirken lassen. Was bedeutet dies für das bewegliche Lager auf der rechten Seite? Mit einem roten Fragezeichen kann man diese Fragestellung grafisch verdeutlichen.

Solche individuellen Abbildungen können weiter im Chat verfeinert werden, falls diese ein Update benötigen. Auch ein Download ist möglich, um das Bild auf dem eigenen Rechner zum Beispiel im Rahmen einer Präsentation zu zeigen. Hierfür einfach rechts oben auf das Download-Icon klicken und das Image im PNG-Format herunterladen.

Nach der Bilderstellung und gezielten Anpassung geht es wieder zurück zur Simulation. Da ich generell einige Fragen bezüglich der Animation habe, ersetze ich rechts unten das Feld „Mechanik-Erklärung“ durch das neue Feld „Mechanik-Tutor“. Dies wurde mit diesem Folgeprompt geändert:
FOLGEPROMPT: Ersetze rechts unten das Feld Mechanik-Erklärung durch einen Chat, den ich für Fragen nutzen kann.

Gemini hat die Anweisung super umgesetzt und rechts unten einen Chat integriert, mit dem ich schnell einzelne Fragestellungen diskutieren kann. Mich hatte interessiert, welche Bedeutung die roten und blauen Streben an der Brückenkonstruktion haben und prompt hat mir Gemini erläutert, dass es sich hierbei um die Druck- und Zugkräfte im Rahmen der Brückenkonstruktion handelt.
Hätte ich diese Frage links im Chat gestellt, so würde die Antwort weitaus länger dauern, da die KI auch den gesamten Code durchgeht. Ich müsste also die Canvas-Funktion deaktivieren und dann im normalen Chat fragen, um anschließend wieder die Anwendung mit aktiver Canvas-Funktion zu öffnen. Dies wäre umständlich. Ein integrierter Chat ist aus meinen Erfahrungen eine tolle Lösung! Gerne mal testen 😉
Autor



