2909 lines
118 KiB
HTML
2909 lines
118 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Rechtwinkliges Dreieck - 3D Geometrie-Rechner</title>
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
background-color: #f0f2f5;
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
color: #1a1a1a;
|
||
margin-bottom: 30px;
|
||
font-size: 2em;
|
||
}
|
||
|
||
.main-container {
|
||
display: grid;
|
||
grid-template-columns: 350px 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.left-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.right-panel {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 20px;
|
||
}
|
||
|
||
.section {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
|
||
border: 1px solid #e0e0e0;
|
||
}
|
||
|
||
h2 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
border-bottom: 2px solid #007acc;
|
||
padding-bottom: 8px;
|
||
font-size: 1.2em;
|
||
}
|
||
|
||
h3 {
|
||
color: #555;
|
||
font-size: 1em;
|
||
margin-top: 15px;
|
||
margin-bottom: 10px;
|
||
background: #f5f5f5;
|
||
padding: 5px 10px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.input-group {
|
||
margin: 10px 0;
|
||
display: grid;
|
||
grid-template-columns: 120px 1fr 40px;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
label {
|
||
font-weight: 500;
|
||
color: #555;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
input {
|
||
padding: 6px 10px;
|
||
font-size: 14px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
width: 100%;
|
||
}
|
||
|
||
input:focus {
|
||
outline: none;
|
||
border-color: #007acc;
|
||
box-shadow: 0 0 0 2px rgba(0,122,204,0.2);
|
||
}
|
||
|
||
input:disabled {
|
||
background-color: #f5f5f5;
|
||
color: #999;
|
||
}
|
||
|
||
select {
|
||
padding: 6px 10px;
|
||
font-size: 14px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
background: white;
|
||
cursor: pointer;
|
||
}
|
||
|
||
select:focus {
|
||
outline: none;
|
||
border-color: #007acc;
|
||
box-shadow: 0 0 0 2px rgba(0,122,204,0.2);
|
||
}
|
||
|
||
.unit {
|
||
color: #777;
|
||
font-size: 0.85em;
|
||
text-align: center;
|
||
}
|
||
|
||
.ratio-input {
|
||
display: flex;
|
||
gap: 5px;
|
||
align-items: center;
|
||
}
|
||
|
||
.ratio-input input {
|
||
text-align: center;
|
||
width: 60px;
|
||
}
|
||
|
||
.ratio-separator {
|
||
color: #555;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
button {
|
||
background-color: #007acc;
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #005a9e;
|
||
}
|
||
|
||
.clear-button {
|
||
background-color: #dc3545;
|
||
}
|
||
|
||
.clear-button:hover {
|
||
background-color: #c82333;
|
||
}
|
||
|
||
.export-button {
|
||
background-color: #28a745;
|
||
}
|
||
|
||
.export-button:hover {
|
||
background-color: #218838;
|
||
}
|
||
|
||
/* Plotly Container */
|
||
#plot3d {
|
||
width: 100%;
|
||
height: 600px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* Koordinaten-Tabelle */
|
||
.coordinates-table {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.coord-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.coord-table th,
|
||
.coord-table td {
|
||
padding: 8px;
|
||
text-align: left;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.coord-table th {
|
||
background: #f5f5f5;
|
||
font-weight: 600;
|
||
position: sticky;
|
||
top: 0;
|
||
}
|
||
|
||
.coord-table tr:nth-child(even) {
|
||
background: #fafafa;
|
||
}
|
||
|
||
.coord-table tr:hover {
|
||
background: #e8f4ff;
|
||
}
|
||
|
||
/* Kaskadenrechner Tabelle */
|
||
#cascade-results table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
#cascade-results th,
|
||
#cascade-results td {
|
||
padding: 8px;
|
||
text-align: center;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
#cascade-results th {
|
||
background: #f5f5f5;
|
||
font-weight: 600;
|
||
}
|
||
|
||
#cascade-results tr:hover {
|
||
background: #e8f4ff;
|
||
cursor: pointer;
|
||
}
|
||
.add-mulde-placeholder {
|
||
text-align: center;
|
||
padding: 8px;
|
||
border: 2px dashed #ccc;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
color: #666;
|
||
font-size: 0.85em;
|
||
transition: all 0.2s ease-in-out;
|
||
}
|
||
.add-mulde-placeholder:hover {
|
||
background: #e8f4ff;
|
||
border-color: #007acc;
|
||
color: #007acc;
|
||
}
|
||
.dark-mode .add-mulde-placeholder {
|
||
border-color: #555;
|
||
color: #999;
|
||
}
|
||
.dark-mode .add-mulde-placeholder:hover {
|
||
background: #3a3a3a;
|
||
border-color: #0096ff;
|
||
color: #0096ff;
|
||
}
|
||
|
||
.point-indicator {
|
||
display: inline-block;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.color-main { background-color: #007acc; }
|
||
.color-inner { background-color: #28a745; }
|
||
.color-trapez { background-color: #dc3545; }
|
||
.color-extended { background-color: #6f42c1; }
|
||
.color-rect { background-color: #ffc107; }
|
||
|
||
/* Ergebnisse */
|
||
.result-grid {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.result-group {
|
||
background: #f8f9fa;
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
border: 1px solid #e9ecef;
|
||
}
|
||
|
||
.result-group h4 {
|
||
margin: 0 0 8px 0;
|
||
color: #495057;
|
||
font-size: 0.9em;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.result {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 4px 0;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.result-label {
|
||
color: #666;
|
||
}
|
||
|
||
.result-value {
|
||
font-weight: 600;
|
||
color: #007acc;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
}
|
||
|
||
/* Converter - Kompakter */
|
||
.converter-section {
|
||
background: #f8f9fa;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
border: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.converter-section h2 {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.converter-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.converter-main-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
}
|
||
|
||
.converter-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.converter-item label {
|
||
font-size: 0.8em;
|
||
margin: 0;
|
||
}
|
||
|
||
.converter-item input {
|
||
padding: 5px 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.converter-ratio {
|
||
display: flex;
|
||
gap: 3px;
|
||
align-items: center;
|
||
}
|
||
|
||
.converter-ratio input {
|
||
text-align: center;
|
||
padding: 5px 4px;
|
||
width: 100%;
|
||
}
|
||
|
||
.converter-ratio .separator {
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.multiplier-section {
|
||
margin-top: 20px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.multiplier-title {
|
||
margin-top: 0 !important;
|
||
margin-bottom: 10px !important;
|
||
font-size: 0.9em !important;
|
||
background: none !important;
|
||
padding: 0 !important;
|
||
border: none !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.multiplier-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr auto 1.2fr auto;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.multiplier-grid input {
|
||
width: 100%;
|
||
}
|
||
|
||
.multiplier-grid button {
|
||
padding: 5px 10px;
|
||
justify-self: start;
|
||
}
|
||
|
||
.multiplier-op {
|
||
font-weight: 500;
|
||
font-size: 1.1em;
|
||
text-align: center;
|
||
}
|
||
|
||
.bottom-layout-grid {
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: 20px;
|
||
margin-top: 20px;
|
||
align-items: start;
|
||
}
|
||
#coordinates-section {
|
||
grid-column: 1;
|
||
}
|
||
.converter-section {
|
||
grid-column: 2;
|
||
}
|
||
|
||
/* Dialog */
|
||
.dialog-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 999;
|
||
}
|
||
|
||
.dialog {
|
||
display: none;
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||
z-index: 1000;
|
||
max-width: 800px;
|
||
width: 90%;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.dialog h3 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
border-bottom: 2px solid #007acc;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.dialog textarea {
|
||
width: 100%;
|
||
height: 400px;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
white-space: pre;
|
||
}
|
||
|
||
/* Plot Controls */
|
||
.plot-controls {
|
||
background: #f5f5f5;
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.plot-controls label {
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.plot-controls input[type="checkbox"] {
|
||
width: auto;
|
||
}
|
||
|
||
/* Dark Mode Styles */
|
||
body.dark-mode {
|
||
background-color: #1a1a1a;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode .section {
|
||
background: #2a2a2a;
|
||
border-color: #404040;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
.dark-mode h1, .dark-mode h2 {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode h3 {
|
||
background: #333;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode label {
|
||
color: #b0b0b0;
|
||
}
|
||
|
||
.dark-mode input {
|
||
background: #333;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
|
||
.dark-mode input:disabled {
|
||
background-color: #252525;
|
||
color: #666;
|
||
}
|
||
|
||
.dark-mode select {
|
||
background: #333;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
|
||
.dark-mode .unit {
|
||
color: #999;
|
||
}
|
||
|
||
.dark-mode .result-group {
|
||
background: #333;
|
||
border-color: #404040;
|
||
}
|
||
|
||
.dark-mode .converter-section {
|
||
background: #333;
|
||
border-color: #404040;
|
||
}
|
||
|
||
.dark-mode .plot-controls {
|
||
background: #333;
|
||
}
|
||
|
||
.dark-mode .coord-table th {
|
||
background: #333;
|
||
}
|
||
|
||
.dark-mode .coord-table tr:nth-child(even) {
|
||
background: #2a2a2a;
|
||
}
|
||
|
||
.dark-mode .coord-table tr:hover {
|
||
background: #3a3a3a;
|
||
}
|
||
|
||
/* Dark Mode Toggle */
|
||
.theme-toggle {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
background: #007acc;
|
||
color: white;
|
||
border: none;
|
||
padding: 8px 12px;
|
||
border-radius: 20px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background 0.3s;
|
||
z-index: 100;
|
||
}
|
||
|
||
.theme-toggle:hover {
|
||
background: #005a9e;
|
||
}
|
||
|
||
.dark-mode .theme-toggle {
|
||
background: #ffc107;
|
||
color: #333;
|
||
}
|
||
|
||
.dark-mode .theme-toggle:hover {
|
||
background: #ffb300;
|
||
}
|
||
|
||
.dark-mode .dialog {
|
||
background: #2a2a2a;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode .dialog h3 {
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode .dialog textarea {
|
||
background: #333;
|
||
color: #e0e0e0;
|
||
border-color: #555;
|
||
}
|
||
|
||
/* Dark Mode für Kaskadenrechner */
|
||
.dark-mode #cascade-results,
|
||
.dark-mode #cascade-detail-content,
|
||
.dark-mode #cascade-lengths,
|
||
.dark-mode #cascade-config-container,
|
||
.dark-mode #cascade-summary-content {
|
||
background: #333;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.dark-mode #cascade-config-container > div > div {
|
||
background: #2a2a2a;
|
||
border-color: #555;
|
||
}
|
||
|
||
.dark-mode #cascade-canvas {
|
||
background: #2a2a2a;
|
||
border-color: #555;
|
||
}
|
||
|
||
.dark-mode #cascade-results table tr:hover {
|
||
background: #3a3a3a;
|
||
}
|
||
|
||
.dark-mode .multiplier-section {
|
||
border-top-color: #404040;
|
||
}
|
||
|
||
.dark-mode .multiplier-title {
|
||
color: #b0b0b0 !important;
|
||
}
|
||
|
||
.dark-mode .multiplier-op,
|
||
.dark-mode .converter-ratio .separator {
|
||
color: #b0b0b0;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.main-container {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.right-panel > div:first-child {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.converter-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
|
||
#cascade-calculator > div {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
@media (max-width: 768px) {
|
||
.bottom-layout-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.bottom-layout-grid > .section {
|
||
grid-column: 1;
|
||
}
|
||
}
|
||
</style>
|
||
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
||
</head>
|
||
<body>
|
||
<button class="theme-toggle" onclick="toggleDarkMode()">🌓</button>
|
||
<h1>Rechtwinkliges Dreieck - 3D Geometrie-Rechner</h1>
|
||
|
||
<div class="main-container">
|
||
<!-- Linke Spalte: Eingabe -->
|
||
<div class="left-panel">
|
||
<div class="section">
|
||
<h2>Eingabe-Parameter</h2>
|
||
|
||
<h3>Hauptwinkel β (BN_Berg)</h3>
|
||
<div class="input-group">
|
||
<label for="beta-degrees">β (Grad)</label>
|
||
<input type="number" id="beta-degrees" onchange="convertBeta(this.id); calculate()" step="0.01">
|
||
<span class="unit">°</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="beta-percent">β (Prozent)</label>
|
||
<input type="number" id="beta-percent" onchange="convertBeta(this.id); calculate()" step="0.01">
|
||
<span class="unit">%</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="beta-ratio">β (Verhältnis)</label>
|
||
<div class="ratio-input">
|
||
<input type="number" id="beta-ratio-left" value="1" onchange="convertBeta('beta-ratio'); calculate()" step="0.01">
|
||
<span class="ratio-separator">:</span>
|
||
<input type="number" id="beta-ratio-right" onchange="convertBeta('beta-ratio'); calculate()" step="0.001">
|
||
</div>
|
||
<span class="unit"></span>
|
||
</div>
|
||
|
||
<h3>Abstand BS2 (SHmax)</h3>
|
||
<div class="input-group">
|
||
<label for="bs2">Vertikaler Abstand</label>
|
||
<input type="number" id="bs2" step="0.001" onchange="calculate()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
|
||
<h3>Innerer Winkel (Straßenneigung)</h3>
|
||
<div class="input-group">
|
||
<label for="zeta-degrees">ζ (Grad)</label>
|
||
<input type="number" id="zeta-degrees" onchange="convertZeta(this.id); calculate()" step="0.01">
|
||
<span class="unit">°</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="zeta-percent">ζ (Prozent)</label>
|
||
<input type="number" id="zeta-percent" onchange="convertZeta(this.id); calculate()" step="0.01">
|
||
<span class="unit">%</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="zeta-ratio">ζ (Verhältnis)</label>
|
||
<div class="ratio-input">
|
||
<input type="number" id="zeta-ratio-left" value="1" onchange="convertZeta('zeta-ratio'); calculate()" step="0.01">
|
||
<span class="ratio-separator">:</span>
|
||
<input type="number" id="zeta-ratio-right" onchange="convertZeta('zeta-ratio'); calculate()" step="0.001">
|
||
</div>
|
||
<span class="unit"></span>
|
||
</div>
|
||
|
||
<h3>Trapez-Erweiterung (SHmin)</h3>
|
||
<div class="input-group">
|
||
<label for="trapezoid-be">Seite BE</label>
|
||
<input type="number" id="trapezoid-be" step="0.01" onchange="handleTrapezoidInput(this.id); calculate()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="trapezoid-ef">Seite EF</label>
|
||
<input type="number" id="trapezoid-ef" step="0.01" onchange="handleTrapezoidInput(this.id); calculate()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="trapezoid-s2f">Seite S2F</label>
|
||
<input type="number" id="trapezoid-s2f" step="0.01" onchange="handleTrapezoidInput(this.id); calculate()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
|
||
<h3>Erweiterte Konstruktion (BN_Tal)</h3>
|
||
<div class="input-group">
|
||
<label for="angle-e-degrees">Winkel E (Grad)</label>
|
||
<input type="number" id="angle-e-degrees" onchange="convertAngleE(this.id); calculate()" step="0.01">
|
||
<span class="unit">°</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="angle-e-percent">Winkel E (Prozent)</label>
|
||
<input type="number" id="angle-e-percent" onchange="convertAngleE(this.id); calculate()" step="0.01">
|
||
<span class="unit">%</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="angle-e-ratio">Winkel E (Verhältnis)</label>
|
||
<div class="ratio-input">
|
||
<input type="number" id="angle-e-ratio-left" value="1" onchange="convertAngleE('angle-e-ratio'); calculate()" step="0.01">
|
||
<span class="ratio-separator">:</span>
|
||
<input type="number" id="angle-e-ratio-right" onchange="convertAngleE('angle-e-ratio'); calculate()" step="0.001">
|
||
</div>
|
||
<span class="unit"></span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="distance-ag">Strecke AG</label>
|
||
<input type="number" id="distance-ag" step="0.01" onchange="handleExtendedInput(this.id); calculate()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
|
||
<div class="button-group">
|
||
<button onclick="calculate()">Berechnen</button>
|
||
<button class="clear-button" onclick="clearAll()">Zurücksetzen</button>
|
||
<button class="export-button" onclick="exportToRhino()">Rhino Export</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechte Spalte: Visualisierung und Ergebnisse -->
|
||
<div class="right-panel">
|
||
<div style="display: grid; grid-template-columns: 1fr 400px; gap: 20px;">
|
||
<!-- 3D Visualisierung -->
|
||
<div class="section">
|
||
<h2>3D Geometrie-Visualisierung</h2>
|
||
|
||
<div class="plot-controls">
|
||
<label><input type="checkbox" id="showGrid" checked onchange="updatePlot()"> Gitter</label>
|
||
<label><input type="checkbox" id="showLabels" checked onchange="updatePlot()"> Beschriftungen</label>
|
||
<label><input type="checkbox" id="showTriangle" checked onchange="updatePlot()"> Hauptdreieck</label>
|
||
<label><input type="checkbox" id="showRect" checked onchange="updatePlot()"> Rechteck</label>
|
||
<label><input type="checkbox" id="showInner" checked onchange="updatePlot()"> Innere Konstruktion</label>
|
||
<label><input type="checkbox" id="showTrapez" checked onchange="updatePlot()"> Trapez</label>
|
||
<label><input type="checkbox" id="showExtended" checked onchange="updatePlot()"> Erweitert</label>
|
||
</div>
|
||
|
||
<div id="plot3d"></div>
|
||
</div>
|
||
|
||
<!-- Ergebnisse -->
|
||
<div class="section">
|
||
<h2>Berechnungsergebnisse</h2>
|
||
|
||
<div class="result-grid">
|
||
<div class="result-group">
|
||
<h4>Berechnete Grundwerte</h4>
|
||
<div class="result">
|
||
<span class="result-label">Seite a (BC):</span>
|
||
<span class="result-value" id="result-calc-a">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Seite b (AC):</span>
|
||
<span class="result-value" id="result-calc-b">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Seite c (AB):</span>
|
||
<span class="result-value" id="result-calc-c">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Winkel α:</span>
|
||
<span class="result-value" id="result-calc-alpha">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="result-group">
|
||
<h4>Hauptdreieck ABC</h4>
|
||
<div class="result">
|
||
<span class="result-label">Fläche:</span>
|
||
<span class="result-value" id="result-area">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Umfang:</span>
|
||
<span class="result-value" id="result-perimeter">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Höhe h_c:</span>
|
||
<span class="result-value" id="result-hc">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="inner-results" class="result-group" style="display: none;">
|
||
<h4>Inneres Dreieck AS1S2</h4>
|
||
<div class="result">
|
||
<span class="result-label">Hypotenuse AS2:</span>
|
||
<span class="result-value" id="result-inner-f">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Winkel ε:</span>
|
||
<span class="result-value" id="result-inner-epsilon">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Winkel ζ:</span>
|
||
<span class="result-value" id="result-inner-zeta">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="trapezoid-results" class="result-group" style="display: none;">
|
||
<h4>Trapez S2FEB</h4>
|
||
<div class="result">
|
||
<span class="result-label">Fläche:</span>
|
||
<span class="result-value" id="result-trapezoid-area">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Seite S2F:</span>
|
||
<span class="result-value" id="result-trapezoid-fs2">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="extended-results" class="result-group" style="display: none;">
|
||
<h4>Erweiterte Konstruktion</h4>
|
||
<div class="result">
|
||
<span class="result-label">Strecke AG:</span>
|
||
<span class="result-value" id="result-extended-ag">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Seite EG:</span>
|
||
<span class="result-value" id="result-extended-eg">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Seite GH:</span>
|
||
<span class="result-value" id="result-extended-gh">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kaskadenrechner -->
|
||
<div class="section" id="cascade-calculator">
|
||
<h2>Kaskadenrechner - Muldenaufteilung</h2>
|
||
|
||
<div style="display: grid; grid-template-columns: 350px 1fr; gap: 20px;">
|
||
<!-- Linke Spalte: Grundparameter -->
|
||
<div>
|
||
<h3>Grundparameter</h3>
|
||
<div class="input-group">
|
||
<label for="cascade-total-length">Gesamtlänge L</label>
|
||
<input type="number" id="cascade-total-length" step="0.1" onchange="updateCascadePreview()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label for="cascade-bankett-width">Bankettbreite</label>
|
||
<input type="number" id="cascade-bankett-width" step="0.1" value="1.0" onchange="updateCascadePreview()">
|
||
<span class="unit">m</span>
|
||
</div>
|
||
|
||
<h3>Berechnete Muldenlängen</h3>
|
||
<div id="cascade-lengths" style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 0.85em;">
|
||
<div class="result">
|
||
<span class="result-label">Typ AG (AS2+S2F+FG):</span>
|
||
<span class="result-value" id="cascade-length-ag">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Typ S2F (vertikal):</span>
|
||
<span class="result-value" id="cascade-length-s2f">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Typ K1 (AS2+S2F):</span>
|
||
<span class="result-value" id="cascade-length-k1">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Typ K2 (S2F+FG):</span>
|
||
<span class="result-value" id="cascade-length-k2">-</span>
|
||
</div>
|
||
<div class="result" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #ddd;">
|
||
<span class="result-label">EF (aktuell):</span>
|
||
<span class="result-value" id="cascade-ef-current" style="color: #28a745;">-</span>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Schnellkonfiguration</h3>
|
||
<div style="margin: 10px 0;">
|
||
<label style="display: flex; align-items: center; gap: 5px; margin-bottom: 10px;">
|
||
<input type="checkbox" id="cascade-bankett-start" checked onchange="updateCascadePreview()">
|
||
Bankett am Anfang
|
||
</label>
|
||
<label style="display: flex; align-items: center; gap: 5px; margin-bottom: 10px;">
|
||
<input type="checkbox" id="cascade-bankett-end" checked onchange="updateCascadePreview()">
|
||
Bankett am Ende
|
||
</label>
|
||
<div style="margin-top: 10px;">
|
||
<label style="display: block; margin-bottom: 5px;">Standard-Muldentyp:</label>
|
||
<select id="cascade-default-type" style="width: 100%; padding: 6px;">
|
||
<option value="AG">Typ AG: AS2 + S2F + FG</option>
|
||
<option value="S2F">Typ S2F: Nur S2F (vertikal)</option>
|
||
<option value="K1">Typ K1: AS2 + S2F</option>
|
||
<option value="K2">Typ K2: S2F + FG</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechte Spalte: Muldenkonfiguration -->
|
||
<div>
|
||
<h3>Muldenkonfiguration</h3>
|
||
<div id="cascade-config-container" style="max-height: 400px; overflow-y: auto; background: #f8f9fa; padding: 15px; border-radius: 4px;">
|
||
</div>
|
||
|
||
<div style="margin-top: 15px; display: flex; gap: 10px;">
|
||
<button onclick="autoFillMulden()">Automatisch auffüllen</button>
|
||
<button onclick="clearAllMulden()" class="clear-button">Alle entfernen</button>
|
||
<button onclick="exportCascadeConfig()" class="export-button">Konfiguration exportieren</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Visuelle Darstellung -->
|
||
<div id="cascade-visual" style="margin-top: 20px;">
|
||
<h3>Kaskadenvisualisierung</h3>
|
||
<canvas id="cascade-canvas" width="1200" height="250" style="width: 100%; border: 1px solid #ddd; border-radius: 4px; background: white;"></canvas>
|
||
</div>
|
||
|
||
<!-- Ergebnistabelle -->
|
||
<div id="cascade-summary" style="margin-top: 20px; display: none;">
|
||
<h3>Zusammenfassung</h3>
|
||
<div id="cascade-summary-content" style="background: #f8f9fa; padding: 15px; border-radius: 4px;">
|
||
</div>
|
||
<div id="cascade-suggestion" style="margin-top: 15px; background: #e3f2fd; padding: 15px; border-radius: 4px; display: none; border: 1px solid #bde0fe;">
|
||
<h4 style="margin-top: 0; color: #0c5460; font-size: 0.9em; font-weight: 600;">Optimierungsvorschlag</h4>
|
||
<p id="suggestion-text" style="font-size: 0.85em; margin-bottom: 10px;"></p>
|
||
<div style="font-size: 0.85em;">
|
||
<div class="result">
|
||
<span class="result-label">Ziel-Wert für S2F:</span>
|
||
<span class="result-value" id="suggestion-s2f" style="color: #dc3545;">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Ziel-Wert für EF:</span>
|
||
<span class="result-value" id="suggestion-ef" style="color: #dc3545;">-</span>
|
||
</div>
|
||
</div>
|
||
<div class="button-group" style="margin-top: 10px; justify-content: flex-start;">
|
||
<button onclick="applySuggestion('s2f')" style="background-color: #28a745; font-size: 12px; padding: 5px 10px;">S2F anwenden</button>
|
||
<button onclick="applySuggestion('ef')" style="background-color: #28a745; font-size: 12px; padding: 5px 10px;">EF anwenden</button>
|
||
</div>
|
||
</div>
|
||
<div id="cascade-distribute-suggestion" style="margin-top: 15px; background: #d4edda; padding: 15px; border-radius: 4px; display: none; border: 1px solid #c3e6cb;">
|
||
<h4 style="margin-top: 0; color: #155724; font-size: 0.9em; font-weight: 600;">Differenz verteilen</h4>
|
||
<p id="distribute-suggestion-text" style="font-size: 0.85em; margin-bottom: 10px;"></p>
|
||
<div style="font-size: 0.85em;">
|
||
<div class="result">
|
||
<span class="result-label">Neuer Ziel-Wert für S2F:</span>
|
||
<span class="result-value" id="distribute-suggestion-s2f" style="color: #dc3545;">-</span>
|
||
</div>
|
||
<div class="result">
|
||
<span class="result-label">Daraus resultierender EF:</span>
|
||
<span class="result-value" id="distribute-suggestion-ef" style="color: #dc3545;">-</span>
|
||
</div>
|
||
</div>
|
||
<div class="button-group" style="margin-top: 10px; justify-content: flex-start;">
|
||
<button onclick="applyDistributeSuggestion('s2f')" style="background-color: #28a745; font-size: 12px; padding: 5px 10px;">S2F anwenden</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bottom-layout-grid">
|
||
<!-- Koordinaten-Tabelle -->
|
||
<div class="section" id="coordinates-section" style="display: none;">
|
||
<h2>Punktkoordinaten (A = Ursprung)</h2>
|
||
<div class="coordinates-table">
|
||
<table class="coord-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Punkt</th>
|
||
<th>X</th>
|
||
<th>Y</th>
|
||
<th>Z</th>
|
||
<th>Gruppe</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="coord-tbody">
|
||
</tbody>
|
||
</table>
|
||
<div class="button-group" style="margin-top: 10px;">
|
||
<button onclick="copyCoordinates()">Koordinaten kopieren</button>
|
||
<button onclick="copyAsCSV()">Als CSV kopieren</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Umrechner - Kompakter -->
|
||
<div class="section converter-section">
|
||
<h2>Neigungs-Umrechner</h2>
|
||
<div class="converter-main-grid">
|
||
<div class="converter-item">
|
||
<label>Prozent</label>
|
||
<input type="number" id="conv-percent" onchange="convertSlope(this.id)" step="0.01">
|
||
</div>
|
||
<div class="converter-item">
|
||
<label>Grad</label>
|
||
<input type="number" id="conv-degrees" onchange="convertSlope(this.id)" step="0.01">
|
||
</div>
|
||
<div class="converter-item">
|
||
<label>Verhältnis</label>
|
||
<div class="converter-ratio">
|
||
<input type="number" id="conv-ratio-left" value="1" onchange="convertSlope('conv-ratio')" step="0.1">
|
||
<span class="separator">:</span>
|
||
<input type="number" id="conv-ratio-right" onchange="convertSlope('conv-ratio')" step="0.01">
|
||
</div>
|
||
</div>
|
||
<div class="converter-item">
|
||
<label>Faktor</label>
|
||
<input type="number" id="conv-factor" disabled step="0.000001">
|
||
</div>
|
||
</div>
|
||
<div class="multiplier-section">
|
||
<h3 class="multiplier-title">Multiplikator</h3>
|
||
<div class="multiplier-grid">
|
||
<input type="number" id="mult-input" placeholder="Wert" step="0.01" onchange="calculateMultiplier()">
|
||
<span class="multiplier-op">×</span>
|
||
<input type="number" id="mult-ratio" placeholder="Verhältnis" step="0.01" onchange="calculateMultiplier()">
|
||
<span class="multiplier-op">=</span>
|
||
<input type="number" id="mult-result" disabled>
|
||
<button onclick="calculateMultiplier()">Berechnen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rhino Export Dialog -->
|
||
<div class="dialog-overlay" id="dialog-overlay" onclick="closeRhinoExport()"></div>
|
||
<div class="dialog" id="rhino-dialog">
|
||
<h3>Rhino Python Script Export</h3>
|
||
<textarea id="rhino-script"></textarea>
|
||
<div class="button-group">
|
||
<button onclick="copyRhinoScript()">Script kopieren</button>
|
||
<button onclick="downloadRhinoScript()">Als Datei speichern</button>
|
||
<button onclick="closeRhinoExport()">Schließen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Konstanten
|
||
const DEFAULTS = {
|
||
beta_ratio_right: 2.5,
|
||
bs2: 0.35,
|
||
zeta_percent: 10,
|
||
trapezoid_ef: 0.2,
|
||
angle_e_degrees: 78.2
|
||
};
|
||
|
||
// Globale Variablen
|
||
let currentResults = null;
|
||
let currentCameraPosition = null;
|
||
let lastModifiedInput = null;
|
||
|
||
// Hilfsfunktionen
|
||
const toRadians = (deg) => deg * Math.PI / 180;
|
||
const toDegrees = (rad) => rad * 180 / Math.PI;
|
||
|
||
// Hauptberechnung
|
||
function calculate() {
|
||
// Steuergrößen einlesen
|
||
let beta = parseFloat(document.getElementById('beta-degrees').value) || null;
|
||
let bs2 = parseFloat(document.getElementById('bs2').value) || null;
|
||
let zeta = parseFloat(document.getElementById('zeta-degrees').value) || null;
|
||
let be_input = parseFloat(document.getElementById('trapezoid-be').value) || null;
|
||
let ef_input = parseFloat(document.getElementById('trapezoid-ef').value) || null;
|
||
let s2f_input = parseFloat(document.getElementById('trapezoid-s2f').value) || null;
|
||
let angleE = parseFloat(document.getElementById('angle-e-degrees').value) || null;
|
||
let ag_input = parseFloat(document.getElementById('distance-ag').value) || null;
|
||
|
||
// Validierung
|
||
if (beta === null || bs2 === null || zeta === null) {
|
||
// alert("Bitte geben Sie Werte für β, BS2 und ζ an.");
|
||
return;
|
||
}
|
||
|
||
// Epsilon aus Zeta berechnen
|
||
if (zeta <= 0 || zeta >= 90) {
|
||
alert("Der Winkel ζ muss zwischen 0° und 90° liegen.");
|
||
return;
|
||
}
|
||
const epsilon = 90 - zeta;
|
||
|
||
if (bs2 <= 0) {
|
||
alert("Der Abstand BS2 muss positiv sein.");
|
||
return;
|
||
}
|
||
if (beta <= 0 || beta >= 90) {
|
||
alert("Der Winkel β muss zwischen 0° und 90° liegen.");
|
||
return;
|
||
}
|
||
|
||
// Grunddreieck berechnen
|
||
const tanBeta = Math.tan(toRadians(beta));
|
||
const tanEpsilon = Math.tan(toRadians(epsilon));
|
||
|
||
const denominator = tanBeta * tanEpsilon - 1;
|
||
if (denominator <= 0) {
|
||
alert("Diese Kombination von β und ε ist geometrisch nicht möglich.");
|
||
return;
|
||
}
|
||
|
||
const a = (bs2 * tanEpsilon) / denominator;
|
||
const b = a * tanBeta;
|
||
|
||
// Dreieck lösen
|
||
const triangle = solveTriangle({ a, b });
|
||
|
||
if (triangle) {
|
||
// Ergebnisse erweitern
|
||
const results = { ...triangle };
|
||
results.area = (results.a * results.b) / 2;
|
||
results.perimeter = results.a + results.b + results.c;
|
||
results.hc = (results.a * results.b) / results.c;
|
||
results.p = (results.b**2) / results.c;
|
||
results.q = (results.a**2) / results.c;
|
||
results.bs2 = bs2;
|
||
results.epsilon = epsilon;
|
||
|
||
// Inneres Dreieck
|
||
const h_as1 = bs2;
|
||
const h_cs1 = results.b - bs2;
|
||
results.innerTriangle = {
|
||
h_cs1: h_cs1,
|
||
h_as1: h_as1,
|
||
a: results.a,
|
||
f: Math.sqrt(h_as1**2 + results.a**2),
|
||
epsilon: results.epsilon,
|
||
zeta: 90 - results.epsilon
|
||
};
|
||
|
||
// Alle Punkte berechnen
|
||
const A = { x: 0, y: 0, z: 0 };
|
||
const B = { x: results.a, y: 0, z: -results.b };
|
||
const C = { x: 0, y: 0, z: -results.b };
|
||
const D = { x: results.a, y: 0, z: 0 };
|
||
const S1 = { x: 0, y: 0, z: -results.b + bs2 };
|
||
const S2 = { x: results.a, y: 0, z: -results.b + bs2 };
|
||
|
||
results.points = { A, B, C, D, S1, S2 };
|
||
|
||
// Trapezoid berechnen wenn Eingabe vorhanden
|
||
if (be_input !== null || ef_input !== null || ag_input !== null || s2f_input !== null) {
|
||
const m_as2 = (S2.z - A.z) / (S2.x - A.x);
|
||
|
||
let E, F, be_len, ef_len, s2f_len;
|
||
|
||
// Bestimme Priorität basierend auf lastModifiedInput
|
||
let useBE = false, useEF = false, useAG = false, useS2F = false;
|
||
|
||
if (lastModifiedInput === 'trapezoid-be' && be_input !== null) {
|
||
useBE = true;
|
||
} else if (lastModifiedInput === 'trapezoid-ef' && ef_input !== null) {
|
||
useEF = true;
|
||
} else if (lastModifiedInput === 'trapezoid-s2f' && s2f_input !== null) {
|
||
useS2F = true;
|
||
} else if (lastModifiedInput === 'distance-ag' && ag_input !== null) {
|
||
useAG = true;
|
||
} else {
|
||
// Fallback: Priorität BE > AG > EF > S2F
|
||
if (be_input !== null) useBE = true;
|
||
else if (ag_input !== null) useAG = true;
|
||
else if (ef_input !== null) useEF = true;
|
||
else if (s2f_input !== null) useS2F = true;
|
||
}
|
||
|
||
if (useBE) {
|
||
be_len = be_input;
|
||
const Ex = B.x + be_len;
|
||
E = { x: Ex, y: 0, z: B.z };
|
||
const Fx = Ex;
|
||
const Fz = m_as2 * Fx;
|
||
F = { x: Fx, y: 0, z: Fz };
|
||
ef_len = Math.abs(F.z - E.z);
|
||
} else if (useAG && angleE !== null) {
|
||
// G liegt auf der Linie AS2 (oder deren Verlängerung) mit Abstand AG von A
|
||
const as2_length = results.innerTriangle.f;
|
||
|
||
// Berechne G auf AS2 mit Abstand AG von A
|
||
const t = ag_input / as2_length;
|
||
const G = {
|
||
x: A.x + t * (S2.x - A.x),
|
||
y: 0,
|
||
z: A.z + t * (S2.z - A.z)
|
||
};
|
||
|
||
// E liegt auf Höhe B.z, berechne E.x aus Winkel bei E
|
||
const angleERad = toRadians(angleE);
|
||
const E_z = B.z;
|
||
|
||
// tan(angleE) = (G.z - E.z) / (G.x - E.x)
|
||
// E.x = G.x - (G.z - E.z) / tan(angleE)
|
||
const E_x = G.x - (G.z - E_z) / Math.tan(angleERad);
|
||
|
||
// Prüfe ob E rechts von B liegt
|
||
if (E_x <= B.x) {
|
||
alert("Mit dieser Kombination von AG und Winkel bei E würde E links von B liegen, was nicht zulässig ist.");
|
||
return;
|
||
}
|
||
|
||
E = { x: E_x, y: 0, z: E_z };
|
||
|
||
// F ist der Schnittpunkt der Vertikalen durch E mit AS2
|
||
const Fx = E.x;
|
||
const Fz = m_as2 * Fx;
|
||
F = { x: Fx, y: 0, z: Fz };
|
||
|
||
be_len = E.x - B.x;
|
||
ef_len = Math.abs(F.z - E.z);
|
||
|
||
// Speichere G für erweiterte Konstruktion
|
||
results.points.G = G;
|
||
} else if (useEF) {
|
||
ef_len = ef_input;
|
||
const x1 = (B.z + ef_len) / m_as2;
|
||
const x2 = (B.z - ef_len) / m_as2;
|
||
|
||
let Ex;
|
||
if (x1 > B.x) {
|
||
Ex = x1;
|
||
F = { x: Ex, y: 0, z: B.z + ef_len };
|
||
} else if (x2 > B.x) {
|
||
Ex = x2;
|
||
F = { x: Ex, y: 0, z: B.z - ef_len };
|
||
} else {
|
||
Ex = B.x + 0.1;
|
||
F = { x: Ex, y: 0, z: m_as2 * Ex };
|
||
}
|
||
|
||
E = { x: Ex, y: 0, z: B.z };
|
||
be_len = E.x - B.x;
|
||
} else if (useS2F) {
|
||
s2f_len = s2f_input;
|
||
|
||
// F.x aus der Länge S2F berechnen
|
||
// s2f_len^2 = (F.x - S2.x)^2 * (1 + m_as2^2)
|
||
const dx = s2f_len / Math.sqrt(1 + m_as2**2);
|
||
const Fx = S2.x + dx;
|
||
const Fz = m_as2 * Fx;
|
||
F = { x: Fx, y: 0, z: Fz };
|
||
|
||
// E ableiten
|
||
const Ex = F.x;
|
||
E = { x: Ex, y: 0, z: B.z };
|
||
|
||
be_len = E.x - B.x;
|
||
ef_len = Math.abs(F.z - E.z);
|
||
}
|
||
|
||
const side_s2b = Math.abs(S2.z - B.z);
|
||
const trapezoid_area = (side_s2b + ef_len) / 2 * be_len;
|
||
|
||
results.trapezoid = {
|
||
E: E, F: F,
|
||
be: be_len, ef: ef_len,
|
||
area: trapezoid_area,
|
||
fs2: s2f_len || Math.sqrt((F.x - S2.x)**2 + (F.z - S2.z)**2)
|
||
};
|
||
|
||
results.points.E = E;
|
||
results.points.F = F;
|
||
|
||
// Erweiterte Konstruktion
|
||
if (angleE && angleE > 0 && angleE < 180) {
|
||
if (!results.points.G) {
|
||
// G wurde noch nicht berechnet (AG war nicht "Chef")
|
||
const angleERad = toRadians(angleE);
|
||
const dx_eg = Math.cos(angleERad);
|
||
const dz_eg = Math.sin(angleERad);
|
||
|
||
const denominator = dz_eg - m_as2 * dx_eg;
|
||
if (Math.abs(denominator) > 0.0001) {
|
||
const t = (m_as2 * E.x - E.z) / denominator;
|
||
|
||
const G = {
|
||
x: E.x + t * dx_eg,
|
||
y: 0,
|
||
z: E.z + t * dz_eg
|
||
};
|
||
|
||
const H = { x: G.x, y: 0, z: E.z };
|
||
|
||
results.extended = {
|
||
G: G,
|
||
H: H,
|
||
angleE: angleE,
|
||
gh: Math.abs(G.z - H.z),
|
||
eg: Math.sqrt((G.x - E.x)**2 + (G.z - E.z)**2),
|
||
eh: Math.abs(H.x - E.x),
|
||
ag: Math.sqrt(G.x**2 + G.z**2)
|
||
};
|
||
|
||
results.points.G = G;
|
||
results.points.H = H;
|
||
}
|
||
} else {
|
||
// G wurde bereits berechnet (AG war "Chef")
|
||
const H = { x: results.points.G.x, y: 0, z: E.z };
|
||
|
||
results.extended = {
|
||
G: results.points.G,
|
||
H: H,
|
||
angleE: angleE,
|
||
gh: Math.abs(results.points.G.z - H.z),
|
||
eg: Math.sqrt((results.points.G.x - E.x)**2 + (results.points.G.z - E.z)**2),
|
||
eh: Math.abs(H.x - E.x),
|
||
ag: ag_input || Math.sqrt(results.points.G.x**2 + results.points.G.z**2)
|
||
};
|
||
|
||
results.points.H = H;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ergebnisse anzeigen
|
||
storeResults(results);
|
||
displayResults(results);
|
||
updateInputs(results);
|
||
displayCoordinatesTable(results);
|
||
createPlot(results);
|
||
|
||
// Kaskadenrechner-Längen aktualisieren
|
||
updateCascadeConfigDisplay();
|
||
updateCascadePreview();
|
||
}
|
||
}
|
||
|
||
function solveTriangle(known) {
|
||
let k = { ...known };
|
||
const MAX_ITERATIONS = 5;
|
||
|
||
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
||
if (k.alpha && !k.beta) k.beta = 90 - k.alpha;
|
||
if (k.beta && !k.alpha) k.alpha = 90 - k.beta;
|
||
|
||
if (k.a && k.b && !k.c) k.c = Math.sqrt(k.a**2 + k.b**2);
|
||
if (k.a && k.c && !k.b) k.b = Math.sqrt(k.c**2 - k.a**2);
|
||
if (k.b && k.c && !k.a) k.a = Math.sqrt(k.c**2 - k.b**2);
|
||
|
||
if (k.a && k.b && !k.alpha) k.alpha = toDegrees(Math.atan(k.a / k.b));
|
||
if (k.a && k.c && !k.alpha) k.alpha = toDegrees(Math.asin(k.a / k.c));
|
||
if (k.b && k.c && !k.alpha) k.alpha = toDegrees(Math.acos(k.b / k.c));
|
||
|
||
if (k.c && k.alpha && !k.a) k.a = k.c * Math.sin(toRadians(k.alpha));
|
||
if (k.c && k.alpha && !k.b) k.b = k.c * Math.cos(toRadians(k.alpha));
|
||
if (k.a && k.alpha && !k.c) k.c = k.a / Math.sin(toRadians(k.alpha));
|
||
if (k.a && k.alpha && !k.b) k.b = k.a / Math.tan(toRadians(k.alpha));
|
||
if (k.b && k.alpha && !k.c) k.c = k.b / Math.cos(toRadians(k.alpha));
|
||
if (k.b && k.alpha && !k.a) k.a = k.b * Math.tan(toRadians(k.alpha));
|
||
|
||
if (k.a && k.b && k.c && k.alpha && k.beta) break;
|
||
}
|
||
|
||
if (k.a && k.b && k.c) {
|
||
return k;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function displayResults(res) {
|
||
document.getElementById('result-calc-a').textContent = `${res.a.toFixed(3)} m`;
|
||
document.getElementById('result-calc-b').textContent = `${res.b.toFixed(3)} m`;
|
||
document.getElementById('result-calc-c').textContent = `${res.c.toFixed(3)} m`;
|
||
document.getElementById('result-calc-alpha').textContent = `${res.alpha.toFixed(2)}°`;
|
||
|
||
document.getElementById('result-area').textContent = `${res.area.toFixed(3)} m²`;
|
||
document.getElementById('result-perimeter').textContent = `${res.perimeter.toFixed(3)} m`;
|
||
document.getElementById('result-hc').textContent = `${res.hc.toFixed(3)} m`;
|
||
|
||
const innerDiv = document.getElementById('inner-results');
|
||
if (res.innerTriangle) {
|
||
document.getElementById('result-inner-f').textContent = `${res.innerTriangle.f.toFixed(3)} m`;
|
||
document.getElementById('result-inner-epsilon').textContent = `${res.innerTriangle.epsilon.toFixed(2)}°`;
|
||
document.getElementById('result-inner-zeta').textContent = `${res.innerTriangle.zeta.toFixed(2)}°`;
|
||
innerDiv.style.display = 'block';
|
||
} else {
|
||
innerDiv.style.display = 'none';
|
||
}
|
||
|
||
const trapDiv = document.getElementById('trapezoid-results');
|
||
if (res.trapezoid) {
|
||
document.getElementById('result-trapezoid-area').textContent = `${res.trapezoid.area.toFixed(3)} m²`;
|
||
document.getElementById('result-trapezoid-fs2').textContent = `${res.trapezoid.fs2.toFixed(3)} m`;
|
||
trapDiv.style.display = 'block';
|
||
} else {
|
||
trapDiv.style.display = 'none';
|
||
}
|
||
|
||
const extDiv = document.getElementById('extended-results');
|
||
if (res.extended) {
|
||
document.getElementById('result-extended-ag').textContent = `${res.extended.ag.toFixed(3)} m`;
|
||
document.getElementById('result-extended-eg').textContent = `${res.extended.eg.toFixed(3)} m`;
|
||
document.getElementById('result-extended-gh').textContent = `${res.extended.gh.toFixed(3)} m`;
|
||
extDiv.style.display = 'block';
|
||
} else {
|
||
extDiv.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function displayCoordinatesTable(results) {
|
||
const tbody = document.getElementById('coord-tbody');
|
||
const section = document.getElementById('coordinates-section');
|
||
|
||
tbody.innerHTML = '';
|
||
|
||
const points = [
|
||
{ name: 'A', coord: results.points.A, group: 'Hauptdreieck', color: 'main' },
|
||
{ name: 'B', coord: results.points.B, group: 'Hauptdreieck', color: 'main' },
|
||
{ name: 'C', coord: results.points.C, group: 'Hauptdreieck', color: 'main' },
|
||
{ name: 'D', coord: results.points.D, group: 'Rechteck', color: 'rect' }
|
||
];
|
||
|
||
if (results.points.S1) {
|
||
points.push(
|
||
{ name: 'S1', coord: results.points.S1, group: 'Innere Konstruktion', color: 'inner' },
|
||
{ name: 'S2', coord: results.points.S2, group: 'Innere Konstruktion', color: 'inner' }
|
||
);
|
||
}
|
||
|
||
if (results.points.E) {
|
||
points.push(
|
||
{ name: 'E', coord: results.points.E, group: 'Trapez', color: 'trapez' },
|
||
{ name: 'F', coord: results.points.F, group: 'Trapez', color: 'trapez' }
|
||
);
|
||
}
|
||
|
||
if (results.points.G) {
|
||
points.push(
|
||
{ name: 'G', coord: results.points.G, group: 'Erweiterte Konstruktion', color: 'extended' },
|
||
{ name: 'H', coord: results.points.H, group: 'Erweiterte Konstruktion', color: 'extended' }
|
||
);
|
||
}
|
||
|
||
points.forEach(p => {
|
||
const row = tbody.insertRow();
|
||
row.innerHTML = `
|
||
<td><span class="point-indicator color-${p.color}"></span>${p.name}</td>
|
||
<td>${p.coord.x.toFixed(3)}</td>
|
||
<td>${p.coord.y.toFixed(3)}</td>
|
||
<td>${p.coord.z.toFixed(3)}</td>
|
||
<td>${p.group}</td>
|
||
`;
|
||
});
|
||
|
||
section.style.display = 'block';
|
||
}
|
||
|
||
function updateInputs(results) {
|
||
// Nur Grad-Feld aktualisieren, OHNE Konvertierung auszulösen
|
||
document.getElementById('zeta-degrees').value = results.innerTriangle.zeta.toFixed(2);
|
||
|
||
// Update abhängige Werte wenn sie nicht gerade aktiv eingegeben werden
|
||
const beInput = document.getElementById('trapezoid-be');
|
||
const efInput = document.getElementById('trapezoid-ef');
|
||
const s2fInput = document.getElementById('trapezoid-s2f');
|
||
const agInput = document.getElementById('distance-ag');
|
||
|
||
// Nur updaten wenn das Feld nicht im Fokus ist
|
||
if (results.trapezoid && document.activeElement !== beInput) {
|
||
beInput.value = results.trapezoid.be.toFixed(3);
|
||
}
|
||
|
||
if (results.trapezoid && document.activeElement !== efInput) {
|
||
efInput.value = results.trapezoid.ef.toFixed(3);
|
||
}
|
||
|
||
if (results.trapezoid && document.activeElement !== s2fInput) {
|
||
s2fInput.value = results.trapezoid.fs2.toFixed(3);
|
||
}
|
||
|
||
// AG kann entweder aus extended oder direkt aus G berechnet werden
|
||
if (document.activeElement !== agInput) {
|
||
if (results.extended && results.extended.ag) {
|
||
agInput.value = results.extended.ag.toFixed(3);
|
||
} else if (results.points && results.points.G) {
|
||
const ag = Math.sqrt(results.points.G.x**2 + results.points.G.z**2);
|
||
agInput.value = ag.toFixed(3);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Plotly 3D Visualisierung
|
||
function createPlot(results) {
|
||
// Kamera-Position speichern falls vorhanden
|
||
const plotDiv = document.getElementById('plot3d');
|
||
if (plotDiv && plotDiv.layout && plotDiv.layout.scene && plotDiv.layout.scene.camera) {
|
||
currentCameraPosition = plotDiv.layout.scene.camera;
|
||
}
|
||
|
||
// Erst den alten Plot sauber entfernen
|
||
Plotly.purge('plot3d');
|
||
|
||
const traces = [];
|
||
const p = results.points;
|
||
|
||
// Hilfsfunktion für Linien
|
||
const createLine = (p1, p2, color, name, showlegend = false, width = 2) => ({
|
||
type: 'scatter3d',
|
||
mode: 'lines',
|
||
x: [p1.x, p2.x],
|
||
y: [p1.y, p2.y],
|
||
z: [p1.z, p2.z],
|
||
line: { color: color, width: width },
|
||
name: name,
|
||
showlegend: showlegend,
|
||
hoverinfo: 'name'
|
||
});
|
||
|
||
// Hauptdreieck ABC
|
||
if (document.getElementById('showTriangle').checked) {
|
||
traces.push({
|
||
type: 'scatter3d',
|
||
mode: 'lines',
|
||
x: [p.A.x, p.B.x, p.C.x, p.A.x],
|
||
y: [p.A.y, p.B.y, p.C.y, p.A.y],
|
||
z: [p.A.z, p.B.z, p.C.z, p.A.z],
|
||
line: { color: '#007acc', width: 4 },
|
||
name: 'Dreieck ABC',
|
||
showlegend: true
|
||
});
|
||
}
|
||
|
||
// Rechteck ABCD
|
||
if (document.getElementById('showRect').checked) {
|
||
traces.push(createLine(p.A, p.D, '#ffc107', 'AD'));
|
||
traces.push(createLine(p.B, p.D, '#ffc107', 'BD'));
|
||
traces.push(createLine(p.C, p.D, '#ffc107', 'CD'));
|
||
}
|
||
|
||
// Innere Konstruktion
|
||
if (document.getElementById('showInner').checked && p.S1 && p.S2) {
|
||
traces.push(createLine(p.A, p.S2, '#28a745', 'AS2', true, 3));
|
||
traces.push(createLine(p.S1, p.S2, '#28a745', 'S1S2'));
|
||
traces.push(createLine(p.B, p.S2, '#28a745', 'BS2'));
|
||
traces.push(createLine(p.S2, p.D, '#28a745', 'S2D'));
|
||
}
|
||
|
||
// Trapez
|
||
if (document.getElementById('showTrapez').checked && p.E && p.F) {
|
||
traces.push(createLine(p.B, p.E, '#dc3545', 'BE'));
|
||
traces.push(createLine(p.E, p.F, '#dc3545', 'EF'));
|
||
traces.push(createLine(p.S2, p.F, '#dc3545', 'S2F'));
|
||
}
|
||
|
||
// Erweiterte Konstruktion
|
||
if (document.getElementById('showExtended').checked && p.G && p.H) {
|
||
traces.push(createLine(p.E, p.G, '#6f42c1', 'EG', true, 4));
|
||
traces.push(createLine(p.G, p.H, '#6f42c1', 'GH'));
|
||
traces.push(createLine(p.F, p.G, '#6f42c1', 'FG'));
|
||
traces.push(createLine(p.A, p.G, '#ff6b6b', 'AG', true, 3));
|
||
traces.push({
|
||
type: 'scatter3d',
|
||
mode: 'lines',
|
||
x: [p.E.x, p.F.x, p.G.x, p.H.x, p.E.x],
|
||
y: [p.E.y, p.F.y, p.G.y, p.H.y, p.E.y],
|
||
z: [p.E.z, p.F.z, p.G.z, p.H.z, p.E.z],
|
||
line: { color: '#6f42c1', width: 2 },
|
||
name: 'EFGH',
|
||
showlegend: false
|
||
});
|
||
}
|
||
|
||
// Punkte hinzufügen
|
||
const pointColors = {
|
||
A: '#007acc', B: '#007acc', C: '#007acc', D: '#ffc107',
|
||
S1: '#28a745', S2: '#28a745',
|
||
E: '#dc3545', F: '#dc3545',
|
||
G: '#6f42c1', H: '#6f42c1'
|
||
};
|
||
|
||
const pointData = {
|
||
x: [], y: [], z: [], text: [], colors: []
|
||
};
|
||
|
||
Object.entries(p).forEach(([name, coord]) => {
|
||
pointData.x.push(coord.x);
|
||
pointData.y.push(coord.y);
|
||
pointData.z.push(coord.z);
|
||
pointData.text.push(name);
|
||
pointData.colors.push(pointColors[name] || '#000000');
|
||
});
|
||
|
||
traces.push({
|
||
type: 'scatter3d',
|
||
mode: 'markers+text',
|
||
x: pointData.x,
|
||
y: pointData.y,
|
||
z: pointData.z,
|
||
text: pointData.text,
|
||
textposition: 'top center',
|
||
textfont: { size: 14, color: '#333' },
|
||
marker: {
|
||
size: 8,
|
||
color: pointData.colors
|
||
},
|
||
name: 'Punkte',
|
||
showlegend: false,
|
||
hoverinfo: 'text',
|
||
hovertext: pointData.text.map((name, i) =>
|
||
`${name}: (${pointData.x[i].toFixed(3)}, ${pointData.y[i].toFixed(3)}, ${pointData.z[i].toFixed(3)})`)
|
||
});
|
||
|
||
// Layout
|
||
const isDarkMode = document.body.classList.contains('dark-mode');
|
||
const layout = {
|
||
title: {
|
||
text: 'Geometrische Konstruktion',
|
||
font: { size: 18, color: isDarkMode ? '#e0e0e0' : '#333' }
|
||
},
|
||
paper_bgcolor: isDarkMode ? '#2a2a2a' : 'white',
|
||
plot_bgcolor: isDarkMode ? '#2a2a2a' : 'white',
|
||
scene: {
|
||
aspectmode: 'data',
|
||
xaxis: {
|
||
title: 'X',
|
||
showspikes: false,
|
||
showgrid: document.getElementById('showGrid').checked,
|
||
gridcolor: isDarkMode ? '#444' : '#ddd',
|
||
color: isDarkMode ? '#e0e0e0' : '#333'
|
||
},
|
||
yaxis: {
|
||
title: 'Y',
|
||
showspikes: false,
|
||
showgrid: document.getElementById('showGrid').checked,
|
||
gridcolor: isDarkMode ? '#444' : '#ddd',
|
||
color: isDarkMode ? '#e0e0e0' : '#333'
|
||
},
|
||
zaxis: {
|
||
title: 'Z',
|
||
showspikes: false,
|
||
showgrid: document.getElementById('showGrid').checked,
|
||
gridcolor: isDarkMode ? '#444' : '#ddd',
|
||
color: isDarkMode ? '#e0e0e0' : '#333'
|
||
},
|
||
camera: currentCameraPosition || {
|
||
eye: { x: 1.5, y: 1.5, z: 1.5 }
|
||
}
|
||
},
|
||
showlegend: true,
|
||
legend: {
|
||
x: 0,
|
||
y: 1,
|
||
bgcolor: isDarkMode ? 'rgba(42,42,42,0.8)' : 'rgba(255,255,255,0.8)',
|
||
font: { color: isDarkMode ? '#e0e0e0' : '#333' }
|
||
},
|
||
margin: { l: 0, r: 0, t: 40, b: 0 }
|
||
};
|
||
|
||
const config = {
|
||
responsive: true,
|
||
displayModeBar: true,
|
||
displaylogo: false,
|
||
modeBarButtonsToRemove: ['select2d', 'lasso2d'],
|
||
toImageButtonOptions: {
|
||
format: 'png',
|
||
filename: 'dreieck_3d',
|
||
height: 800,
|
||
width: 1200,
|
||
scale: 1
|
||
}
|
||
};
|
||
|
||
Plotly.newPlot('plot3d', traces, layout, config);
|
||
}
|
||
|
||
function updatePlot() {
|
||
if (currentResults) {
|
||
createPlot(currentResults);
|
||
}
|
||
}
|
||
|
||
// Hilfsfunktionen
|
||
function clearAll() {
|
||
// Reset tracking
|
||
lastModifiedInput = null;
|
||
currentCameraPosition = null;
|
||
|
||
// Inputs zurücksetzen
|
||
document.getElementById('beta-degrees').value = '';
|
||
document.getElementById('beta-percent').value = '';
|
||
document.getElementById('beta-ratio-left').value = '1';
|
||
document.getElementById('beta-ratio-right').value = '';
|
||
document.getElementById('bs2').value = DEFAULTS.bs2;
|
||
document.getElementById('zeta-degrees').value = '';
|
||
document.getElementById('zeta-percent').value = '';
|
||
document.getElementById('zeta-ratio-left').value = '1';
|
||
document.getElementById('zeta-ratio-right').value = '';
|
||
document.getElementById('trapezoid-be').value = '';
|
||
document.getElementById('trapezoid-ef').value = DEFAULTS.trapezoid_ef;
|
||
document.getElementById('trapezoid-s2f').value = '';
|
||
document.getElementById('angle-e-degrees').value = '';
|
||
document.getElementById('angle-e-percent').value = '';
|
||
document.getElementById('angle-e-ratio-left').value = '1';
|
||
document.getElementById('angle-e-ratio-right').value = '';
|
||
document.getElementById('distance-ag').value = '';
|
||
|
||
// Umrechner zurücksetzen
|
||
document.getElementById('conv-percent').value = '';
|
||
document.getElementById('conv-degrees').value = '';
|
||
document.getElementById('conv-ratio-left').value = '1';
|
||
document.getElementById('conv-ratio-right').value = '';
|
||
document.getElementById('conv-factor').value = '';
|
||
|
||
// Ergebnisse
|
||
document.querySelectorAll('.result-value').forEach(el => el.textContent = '-');
|
||
document.getElementById('inner-results').style.display = 'none';
|
||
document.getElementById('trapezoid-results').style.display = 'none';
|
||
document.getElementById('extended-results').style.display = 'none';
|
||
document.getElementById('coordinates-section').style.display = 'none';
|
||
|
||
// Plot leeren
|
||
Plotly.purge('plot3d');
|
||
}
|
||
|
||
function handleTrapezoidInput(sourceId) {
|
||
lastModifiedInput = sourceId;
|
||
}
|
||
|
||
function handleExtendedInput(sourceId) {
|
||
lastModifiedInput = sourceId;
|
||
}
|
||
|
||
function handleAngleInput(sourceId) {
|
||
// Diese Funktion wird nicht mehr benötigt, da wir nur noch Zeta haben
|
||
}
|
||
|
||
function convertBeta(sourceId) {
|
||
const degreesInput = document.getElementById('beta-degrees');
|
||
const percentInput = document.getElementById('beta-percent');
|
||
const ratioLeftInput = document.getElementById('beta-ratio-left');
|
||
const ratioRightInput = document.getElementById('beta-ratio-right');
|
||
|
||
let value;
|
||
if (sourceId === 'beta-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
const right = parseFloat(ratioRightInput.value);
|
||
if (isNaN(right)) return;
|
||
value = right / left;
|
||
sourceId = 'beta-ratio';
|
||
} else {
|
||
value = parseFloat(document.getElementById(sourceId).value);
|
||
if (isNaN(value)) return;
|
||
}
|
||
|
||
let slope = 0;
|
||
switch (sourceId) {
|
||
case 'beta-degrees':
|
||
if (value <= 0 || value >= 90) return;
|
||
slope = Math.tan(toRadians(value));
|
||
break;
|
||
case 'beta-percent':
|
||
if (value < 0) return;
|
||
slope = value / 100;
|
||
break;
|
||
case 'beta-ratio':
|
||
if (value <= 0) return;
|
||
slope = 1 / value;
|
||
break;
|
||
}
|
||
|
||
if (sourceId !== 'beta-degrees') {
|
||
degreesInput.value = toDegrees(Math.atan(slope)).toFixed(2);
|
||
}
|
||
if (sourceId !== 'beta-percent') {
|
||
percentInput.value = (slope * 100).toFixed(2);
|
||
}
|
||
if (sourceId !== 'beta-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
ratioRightInput.value = slope === 0 ? '' : (left / slope).toFixed(3);
|
||
}
|
||
}
|
||
|
||
function convertZeta(sourceId) {
|
||
const degreesInput = document.getElementById('zeta-degrees');
|
||
const percentInput = document.getElementById('zeta-percent');
|
||
const ratioLeftInput = document.getElementById('zeta-ratio-left');
|
||
const ratioRightInput = document.getElementById('zeta-ratio-right');
|
||
|
||
let value;
|
||
if (sourceId === 'zeta-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
const right = parseFloat(ratioRightInput.value);
|
||
if (isNaN(right)) return;
|
||
value = right / left;
|
||
sourceId = 'zeta-ratio';
|
||
} else {
|
||
value = parseFloat(document.getElementById(sourceId).value);
|
||
if (isNaN(value)) return;
|
||
}
|
||
|
||
let slope = 0;
|
||
switch (sourceId) {
|
||
case 'zeta-degrees':
|
||
if (value <= 0 || value >= 90) return;
|
||
slope = Math.tan(toRadians(value));
|
||
break;
|
||
case 'zeta-percent':
|
||
if (value < 0) return;
|
||
slope = value / 100;
|
||
break;
|
||
case 'zeta-ratio':
|
||
if (value <= 0) return;
|
||
slope = 1 / value;
|
||
break;
|
||
}
|
||
|
||
if (sourceId !== 'zeta-degrees') {
|
||
degreesInput.value = toDegrees(Math.atan(slope)).toFixed(2);
|
||
}
|
||
if (sourceId !== 'zeta-percent') {
|
||
percentInput.value = (slope * 100).toFixed(2);
|
||
}
|
||
if (sourceId !== 'zeta-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
ratioRightInput.value = slope === 0 ? '' : (left / slope).toFixed(3);
|
||
}
|
||
}
|
||
|
||
function convertAngleE(sourceId) {
|
||
const degreesInput = document.getElementById('angle-e-degrees');
|
||
const percentInput = document.getElementById('angle-e-percent');
|
||
const ratioLeftInput = document.getElementById('angle-e-ratio-left');
|
||
const ratioRightInput = document.getElementById('angle-e-ratio-right');
|
||
|
||
let value;
|
||
if (sourceId === 'angle-e-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
const right = parseFloat(ratioRightInput.value);
|
||
if (isNaN(right)) return;
|
||
value = right / left;
|
||
sourceId = 'angle-e-ratio';
|
||
} else {
|
||
value = parseFloat(document.getElementById(sourceId).value);
|
||
if (isNaN(value)) return;
|
||
}
|
||
|
||
let slope = 0;
|
||
switch (sourceId) {
|
||
case 'angle-e-degrees':
|
||
if (value <= 0 || value >= 90) return;
|
||
slope = Math.tan(toRadians(value));
|
||
break;
|
||
case 'angle-e-percent':
|
||
if (value < 0) return;
|
||
slope = value / 100;
|
||
break;
|
||
case 'angle-e-ratio':
|
||
if (value <= 0) return;
|
||
slope = 1 / value;
|
||
break;
|
||
}
|
||
|
||
if (sourceId !== 'angle-e-degrees') {
|
||
degreesInput.value = toDegrees(Math.atan(slope)).toFixed(2);
|
||
}
|
||
if (sourceId !== 'angle-e-percent') {
|
||
percentInput.value = (slope * 100).toFixed(2);
|
||
}
|
||
if (sourceId !== 'angle-e-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
ratioRightInput.value = slope === 0 ? '' : (left / slope).toFixed(3);
|
||
}
|
||
}
|
||
|
||
function convertSlope(sourceId) {
|
||
const percentInput = document.getElementById('conv-percent');
|
||
const degreesInput = document.getElementById('conv-degrees');
|
||
const ratioLeftInput = document.getElementById('conv-ratio-left');
|
||
const ratioRightInput = document.getElementById('conv-ratio-right');
|
||
const factorInput = document.getElementById('conv-factor');
|
||
|
||
let value;
|
||
if (sourceId === 'conv-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
const right = parseFloat(ratioRightInput.value);
|
||
if (isNaN(right)) return;
|
||
value = right / left;
|
||
sourceId = 'conv-ratio';
|
||
} else {
|
||
value = parseFloat(document.getElementById(sourceId).value);
|
||
if (isNaN(value)) return;
|
||
}
|
||
|
||
let slope = 0;
|
||
switch (sourceId) {
|
||
case 'conv-percent':
|
||
if (value < 0) return;
|
||
slope = value / 100;
|
||
break;
|
||
case 'conv-degrees':
|
||
if (value <= 0 || value >= 90) return;
|
||
slope = Math.tan(toRadians(value));
|
||
break;
|
||
case 'conv-ratio':
|
||
if (value <= 0) return;
|
||
slope = 1 / value;
|
||
break;
|
||
}
|
||
|
||
if (sourceId !== 'conv-percent') {
|
||
percentInput.value = (slope * 100).toFixed(2);
|
||
}
|
||
if (sourceId !== 'conv-degrees') {
|
||
degreesInput.value = toDegrees(Math.atan(slope)).toFixed(2);
|
||
}
|
||
if (sourceId !== 'conv-ratio') {
|
||
const left = parseFloat(ratioLeftInput.value) || 1;
|
||
ratioRightInput.value = slope === 0 ? '' : (left / slope).toFixed(3);
|
||
}
|
||
|
||
// Faktor berechnen
|
||
factorInput.value = slope.toFixed(6);
|
||
|
||
// Multiplikator-Verhältnis automatisch setzen
|
||
const multRatioInput = document.getElementById('mult-ratio');
|
||
if (sourceId === 'conv-ratio' && !isNaN(value)) {
|
||
multRatioInput.value = value.toFixed(3);
|
||
calculateMultiplier();
|
||
}
|
||
}
|
||
|
||
// Koordinaten kopieren
|
||
function copyCoordinates() {
|
||
const tbody = document.getElementById('coord-tbody');
|
||
let text = 'Punkt\tX\tY\tZ\tGruppe\n';
|
||
|
||
Array.from(tbody.rows).forEach(row => {
|
||
const cells = row.cells;
|
||
text += `${cells[0].textContent}\t${cells[1].textContent}\t${cells[2].textContent}\t${cells[3].textContent}\t${cells[4].textContent}\n`;
|
||
});
|
||
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
alert('Koordinaten kopiert!');
|
||
});
|
||
}
|
||
|
||
function copyAsCSV() {
|
||
const tbody = document.getElementById('coord-tbody');
|
||
let csv = 'Punkt,X,Y,Z,Gruppe\n';
|
||
|
||
Array.from(tbody.rows).forEach(row => {
|
||
const cells = row.cells;
|
||
csv += `${cells[0].textContent},${cells[1].textContent},${cells[2].textContent},${cells[3].textContent},${cells[4].textContent}\n`;
|
||
});
|
||
|
||
navigator.clipboard.writeText(csv).then(() => {
|
||
alert('CSV-Daten kopiert!');
|
||
});
|
||
}
|
||
|
||
// Export-Funktionen
|
||
function storeResults(results) {
|
||
currentResults = results;
|
||
}
|
||
|
||
function exportToRhino() {
|
||
if (!currentResults) {
|
||
alert("Bitte zuerst berechnen!");
|
||
return;
|
||
}
|
||
|
||
const script = generateRhinoScript(currentResults);
|
||
document.getElementById('rhino-script').value = script;
|
||
document.getElementById('dialog-overlay').style.display = 'block';
|
||
document.getElementById('rhino-dialog').style.display = 'block';
|
||
}
|
||
|
||
function generateRhinoScript(results) {
|
||
const p = results.points;
|
||
let script = `import rhinoscriptsyntax as rs
|
||
|
||
# Geometrie-Export aus Dreieck-Rechner
|
||
# Datum: ${new Date().toLocaleString('de-DE')}
|
||
|
||
# === PUNKTE DEFINIEREN ===
|
||
points = {
|
||
'A': (${p.A.x.toFixed(3)}, ${p.A.y.toFixed(3)}, ${p.A.z.toFixed(3)}),
|
||
'B': (${p.B.x.toFixed(3)}, ${p.B.y.toFixed(3)}, ${p.B.z.toFixed(3)}),
|
||
'C': (${p.C.x.toFixed(3)}, ${p.C.y.toFixed(3)}, ${p.C.z.toFixed(3)}),
|
||
'D': (${p.D.x.toFixed(3)}, ${p.D.y.toFixed(3)}, ${p.D.z.toFixed(3)}),`;
|
||
|
||
if (p.S1 && p.S2) {
|
||
script += `
|
||
'S1': (${p.S1.x.toFixed(3)}, ${p.S1.y.toFixed(3)}, ${p.S1.z.toFixed(3)}),
|
||
'S2': (${p.S2.x.toFixed(3)}, ${p.S2.y.toFixed(3)}, ${p.S2.z.toFixed(3)}),`;
|
||
}
|
||
|
||
if (p.E && p.F) {
|
||
script += `
|
||
'E': (${p.E.x.toFixed(3)}, ${p.E.y.toFixed(3)}, ${p.E.z.toFixed(3)}),
|
||
'F': (${p.F.x.toFixed(3)}, ${p.F.y.toFixed(3)}, ${p.F.z.toFixed(3)}),`;
|
||
}
|
||
|
||
if (p.G && p.H) {
|
||
script += `
|
||
'G': (${p.G.x.toFixed(3)}, ${p.G.y.toFixed(3)}, ${p.G.z.toFixed(3)}),
|
||
'H': (${p.H.x.toFixed(3)}, ${p.H.y.toFixed(3)}, ${p.H.z.toFixed(3)}),`;
|
||
}
|
||
|
||
script += `
|
||
}
|
||
|
||
# === PUNKTE HINZUFÜGEN ===
|
||
for name, coords in points.items():
|
||
pt = rs.AddPoint(coords)
|
||
rs.AddTextDot(name, coords)
|
||
|
||
# === STRECKEN ZEICHNEN ===
|
||
# Hauptdreieck ABC
|
||
rs.AddPolyline([points['A'], points['B'], points['C'], points['A']])
|
||
|
||
# Rechteck ABCD
|
||
rs.AddLine(points['A'], points['D'])
|
||
rs.AddLine(points['B'], points['D'])
|
||
rs.AddLine(points['C'], points['D'])
|
||
`;
|
||
|
||
if (p.S1 && p.S2) {
|
||
script += `
|
||
# Innere Konstruktion
|
||
rs.AddLine(points['A'], points['S2']) # Hypotenuse AS2
|
||
rs.AddLine(points['S1'], points['S2']) # Basis S1S2
|
||
rs.AddLine(points['B'], points['S2']) # Vertikale BS2
|
||
rs.AddLine(points['S2'], points['D']) # Verbindung S2D
|
||
`;
|
||
}
|
||
|
||
if (p.E && p.F) {
|
||
script += `
|
||
# Trapez-Konstruktion
|
||
rs.AddLine(points['B'], points['E']) # BE
|
||
rs.AddLine(points['E'], points['F']) # EF (Höhe)
|
||
rs.AddLine(points['S2'], points['F']) # S2F
|
||
`;
|
||
}
|
||
|
||
if (p.G && p.H) {
|
||
script += `
|
||
# Erweiterte Konstruktion
|
||
rs.AddLine(points['E'], points['G']) # EG (Hauptlinie)
|
||
rs.AddLine(points['G'], points['H']) # GH
|
||
rs.AddLine(points['F'], points['G']) # FG
|
||
rs.AddLine(points['A'], points['G']) # AG
|
||
rs.AddPolyline([points['E'], points['F'], points['G'], points['H'], points['E']])
|
||
`;
|
||
}
|
||
|
||
script += `
|
||
# === LAYER ORGANISATION ===
|
||
layers = {
|
||
'Hauptdreieck': [0, 122, 204], # Blau
|
||
'Innere_Konstruktion': [40, 167, 69], # Grün
|
||
'Trapez': [220, 53, 69], # Rot
|
||
'Erweiterte_Konstruktion': [111, 66, 193] # Lila
|
||
}
|
||
|
||
for name, color in layers.items():
|
||
if not rs.IsLayer(name):
|
||
rs.AddLayer(name, color)
|
||
|
||
# === BERECHNETE WERTE ===
|
||
print("\\n=== BERECHNETE WERTE ===")
|
||
print(f"Seite a (BC) = ${results.a.toFixed(3)} m")
|
||
print(f"Seite b (AC) = ${results.b.toFixed(3)} m")
|
||
print(f"Seite c (AB) = ${results.c.toFixed(3)} m")
|
||
print(f"Winkel Alpha = ${results.alpha.toFixed(2)}°")
|
||
print(f"Winkel Beta = ${results.beta.toFixed(2)}°")
|
||
print(f"Fläche = ${results.area.toFixed(3)} m²")
|
||
`;
|
||
|
||
if (results.trapezoid) {
|
||
script += `print(f"Trapez-Fläche = ${results.trapezoid.area.toFixed(3)} m²")
|
||
`;
|
||
}
|
||
|
||
if (results.extended) {
|
||
script += `print(f"Strecke AG = ${results.extended.ag.toFixed(3)} m")
|
||
`;
|
||
}
|
||
|
||
script += `
|
||
print("\\nImport abgeschlossen!")
|
||
`;
|
||
|
||
return script;
|
||
}
|
||
|
||
function copyRhinoScript() {
|
||
const textarea = document.getElementById('rhino-script');
|
||
textarea.select();
|
||
document.execCommand('copy');
|
||
alert('Script kopiert!');
|
||
}
|
||
|
||
function downloadRhinoScript() {
|
||
const script = document.getElementById('rhino-script').value;
|
||
const blob = new Blob([script], { type: 'text/plain' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `dreieck_geometrie_${new Date().toISOString().slice(0,10)}.py`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function closeRhinoExport() {
|
||
document.getElementById('dialog-overlay').style.display = 'none';
|
||
document.getElementById('rhino-dialog').style.display = 'none';
|
||
}
|
||
|
||
// Dark Mode Toggle
|
||
function toggleDarkMode() {
|
||
document.body.classList.toggle('dark-mode');
|
||
localStorage.setItem('darkMode', document.body.classList.contains('dark-mode'));
|
||
}
|
||
|
||
// Multiplikator berechnen
|
||
function calculateMultiplier() {
|
||
const inputValue = parseFloat(document.getElementById('mult-input').value);
|
||
const ratioValue = parseFloat(document.getElementById('mult-ratio').value);
|
||
|
||
if (!isNaN(inputValue) && !isNaN(ratioValue)) {
|
||
const result = inputValue * ratioValue;
|
||
document.getElementById('mult-result').value = result.toFixed(3);
|
||
} else {
|
||
document.getElementById('mult-result').value = '';
|
||
}
|
||
}
|
||
|
||
// Kaskadenrechner Funktionen
|
||
let cascadeConfig = {
|
||
mulden: [],
|
||
bankettStart: true,
|
||
bankettEnd: true,
|
||
bankettWidth: 1.0,
|
||
totalLength: 0
|
||
};
|
||
|
||
function updateCascadeLengths() {
|
||
if (!currentResults || !currentResults.innerTriangle) {
|
||
document.getElementById('cascade-length-ag').textContent = '-';
|
||
document.getElementById('cascade-length-s2f').textContent = '-';
|
||
document.getElementById('cascade-length-k1').textContent = '-';
|
||
document.getElementById('cascade-length-k2').textContent = '-';
|
||
document.getElementById('cascade-ef-current').textContent = '-';
|
||
return;
|
||
}
|
||
|
||
const res = currentResults;
|
||
const as2_length = res.innerTriangle.f;
|
||
|
||
// Aktueller EF-Wert
|
||
let ef_current = '-';
|
||
if (res.trapezoid && res.trapezoid.ef) {
|
||
ef_current = res.trapezoid.ef.toFixed(3) + ' m';
|
||
document.getElementById('cascade-ef-current').textContent = ef_current;
|
||
}
|
||
|
||
// S2F basierend auf aktuellem EF
|
||
let s2f_length = 0;
|
||
if (res.trapezoid && res.trapezoid.fs2) {
|
||
s2f_length = res.trapezoid.fs2;
|
||
}
|
||
|
||
// FG Länge (wenn erweiterte Konstruktion vorhanden)
|
||
let fg_length = 0;
|
||
if (res.extended && res.points.F && res.points.G) {
|
||
fg_length = Math.sqrt(
|
||
Math.pow(res.points.G.x - res.points.F.x, 2) +
|
||
Math.pow(res.points.G.z - res.points.F.z, 2)
|
||
);
|
||
}
|
||
|
||
// Update Anzeige
|
||
document.getElementById('cascade-length-ag').textContent =
|
||
(as2_length + s2f_length + fg_length).toFixed(3) + ' m';
|
||
document.getElementById('cascade-length-s2f').textContent =
|
||
s2f_length.toFixed(3) + ' m';
|
||
document.getElementById('cascade-length-k1').textContent =
|
||
(as2_length + s2f_length).toFixed(3) + ' m';
|
||
document.getElementById('cascade-length-k2').textContent =
|
||
(s2f_length + fg_length).toFixed(3) + ' m';
|
||
}
|
||
|
||
function getMuldenLength(type) {
|
||
if (!currentResults || !currentResults.innerTriangle) return 0;
|
||
|
||
const res = currentResults;
|
||
const as2_length = res.innerTriangle.f;
|
||
|
||
let s2f_length = 0;
|
||
if (res.trapezoid && res.trapezoid.fs2) {
|
||
s2f_length = res.trapezoid.fs2;
|
||
}
|
||
|
||
let fg_length = 0;
|
||
if (res.extended && res.points.F && res.points.G) {
|
||
fg_length = Math.sqrt(
|
||
Math.pow(res.points.G.x - res.points.F.x, 2) +
|
||
Math.pow(res.points.G.z - res.points.F.z, 2)
|
||
);
|
||
}
|
||
|
||
switch(type) {
|
||
case 'AG': return as2_length + s2f_length + fg_length;
|
||
case 'S2F': return s2f_length;
|
||
case 'K1': return as2_length + s2f_length;
|
||
case 'K2': return s2f_length + fg_length;
|
||
default: return 0;
|
||
}
|
||
}
|
||
|
||
function addMulde(index) { // index kann undefined sein, um am Ende hinzuzufügen
|
||
const defaultType = document.getElementById('cascade-default-type').value;
|
||
|
||
const newMulde = {
|
||
type: defaultType,
|
||
bankettAfter: true // Standardmäßig mit Bankett danach, UI blendet es für die letzte aus
|
||
};
|
||
|
||
const insertionIndex = (index === undefined || index === null) ? cascadeConfig.mulden.length : index;
|
||
cascadeConfig.mulden.splice(insertionIndex, 0, newMulde);
|
||
|
||
// Wenn am Ende hinzugefügt wird, muss die vorherige Mulde ggf. ihr Bankett bekommen
|
||
// Dies wird durch updateCascadeConfigDisplay() und die Logik dort gehandhabt
|
||
updateCascadeConfigDisplay();
|
||
updateCascadePreview();
|
||
}
|
||
|
||
function removeMulde(index) {
|
||
cascadeConfig.mulden.splice(index, 1);
|
||
updateCascadeConfigDisplay();
|
||
updateCascadePreview();
|
||
}
|
||
|
||
function updateMuldeType(index, type) {
|
||
cascadeConfig.mulden[index].type = type;
|
||
updateCascadePreview();
|
||
}
|
||
|
||
function toggleBankettAfter(index) {
|
||
cascadeConfig.mulden[index].bankettAfter = !cascadeConfig.mulden[index].bankettAfter;
|
||
updateCascadePreview();
|
||
}
|
||
|
||
function updateCascadeConfigDisplay() {
|
||
const container = document.getElementById('cascade-config-container');
|
||
const isDarkMode = document.body.classList.contains('dark-mode');
|
||
|
||
let html = '<div style="display: flex; flex-direction: column; gap: 5px;">';
|
||
|
||
// Platzhalter am Anfang, um die erste Mulde hinzuzufügen
|
||
html += `<div class="add-mulde-placeholder" onclick="addMulde(0)"><span>${cascadeConfig.mulden.length === 0 ? '+ Erste Mulde hinzufügen' : '+ Mulde am Anfang einfügen'}</span></div>`;
|
||
|
||
if (cascadeConfig.mulden.length > 0) {
|
||
cascadeConfig.mulden.forEach((mulde, index) => {
|
||
const muldeLength = getMuldenLength(mulde.type);
|
||
const muldeBg = isDarkMode ? '#2a2a2a' : 'white';
|
||
const muldeBorder = isDarkMode ? '#555' : '#ddd';
|
||
|
||
// Die Mulde selbst rendern
|
||
html += `
|
||
<div style="background: ${muldeBg}; padding: 10px; border: 1px solid ${muldeBorder}; border-radius: 4px;">
|
||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||
<strong>Mulde ${index + 1}</strong>
|
||
<button onclick="removeMulde(${index})" style="background: #dc3545; padding: 4px 8px; font-size: 12px;">×</button>
|
||
</div>
|
||
<div style="margin-top: 8px; display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center;">
|
||
<select onchange="updateMuldeType(${index}, this.value)" style="padding: 4px;">
|
||
<option value="AG" ${mulde.type === 'AG' ? 'selected' : ''}>Typ AG (${getMuldenLength('AG').toFixed(2)}m)</option>
|
||
<option value="S2F" ${mulde.type === 'S2F' ? 'selected' : ''}>Typ S2F (${getMuldenLength('S2F').toFixed(2)}m)</option>
|
||
<option value="K1" ${mulde.type === 'K1' ? 'selected' : ''}>Typ K1 (${getMuldenLength('K1').toFixed(2)}m)</option>
|
||
<option value="K2" ${mulde.type === 'K2' ? 'selected' : ''}>Typ K2 (${getMuldenLength('K2').toFixed(2)}m)</option>
|
||
</select>
|
||
<span style="font-size: 0.85em; color: #007acc;">${muldeLength.toFixed(3)} m</span>
|
||
</div>
|
||
${index < cascadeConfig.mulden.length - 1 ? `
|
||
<label style="display: flex; align-items: center; gap: 5px; margin-top: 8px; font-size: 0.85em;">
|
||
<input type="checkbox" ${mulde.bankettAfter ? 'checked' : ''} onchange="toggleBankettAfter(${index})">
|
||
Bankett nach dieser Mulde
|
||
</label>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
// Platzhalter NACH der aktuellen Mulde
|
||
html += `<div class="add-mulde-placeholder" onclick="addMulde(${index + 1})"><span>+ Mulde hier einfügen</span></div>`;
|
||
});
|
||
}
|
||
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function updateCascadePreview() {
|
||
cascadeConfig.bankettStart = document.getElementById('cascade-bankett-start').checked;
|
||
cascadeConfig.bankettEnd = document.getElementById('cascade-bankett-end').checked;
|
||
cascadeConfig.bankettWidth = parseFloat(document.getElementById('cascade-bankett-width').value) || 1.0;
|
||
cascadeConfig.totalLength = parseFloat(document.getElementById('cascade-total-length').value) || 0;
|
||
|
||
// Referenzlängen, Visualisierung und Zusammenfassung aktualisieren
|
||
updateCascadeLengths();
|
||
drawCascadeVisualization2();
|
||
|
||
if (cascadeConfig.mulden.length > 0) {
|
||
updateCascadeSummary();
|
||
} else {
|
||
document.getElementById('cascade-summary').style.display = 'none';
|
||
document.getElementById('cascade-suggestion').style.display = 'none';
|
||
document.getElementById('cascade-distribute-suggestion').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function autoFillMulden() {
|
||
const totalLength = parseFloat(document.getElementById('cascade-total-length').value);
|
||
const defaultType = document.getElementById('cascade-default-type').value;
|
||
|
||
if (!totalLength || totalLength <= 0) {
|
||
alert('Bitte Gesamtlänge eingeben');
|
||
return;
|
||
}
|
||
|
||
if (!currentResults || !currentResults.innerTriangle) {
|
||
alert('Bitte zuerst Hauptgeometrie berechnen');
|
||
return;
|
||
}
|
||
|
||
cascadeConfig.mulden = [];
|
||
|
||
// Verfügbare Länge berechnen
|
||
let availableLength = totalLength;
|
||
if (cascadeConfig.bankettStart) availableLength -= cascadeConfig.bankettWidth;
|
||
if (cascadeConfig.bankettEnd) availableLength -= cascadeConfig.bankettWidth;
|
||
|
||
const muldenLength = getMuldenLength(defaultType);
|
||
|
||
if (muldenLength <= 0) {
|
||
alert('Muldenlänge kann nicht berechnet werden');
|
||
return;
|
||
}
|
||
|
||
// Maximale Anzahl ohne Zwischenbankette
|
||
let maxMulden = Math.floor(availableLength / muldenLength);
|
||
|
||
// Mit Zwischenbanketten probieren
|
||
for (let n = maxMulden; n >= 1; n--) {
|
||
const mitBanketten = availableLength - (n - 1) * cascadeConfig.bankettWidth;
|
||
if (mitBanketten >= n * muldenLength) {
|
||
// Diese Konfiguration passt
|
||
for (let i = 0; i < n; i++) {
|
||
cascadeConfig.mulden.push({
|
||
type: defaultType,
|
||
bankettAfter: i < n - 1
|
||
});
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
updateCascadeConfigDisplay();
|
||
updateCascadePreview();
|
||
}
|
||
|
||
function clearAllMulden() {
|
||
cascadeConfig.mulden = [];
|
||
updateCascadeConfigDisplay();
|
||
updateCascadePreview(); // This will hide summary and suggestion
|
||
}
|
||
|
||
function drawCascadeVisualization2() {
|
||
const canvas = document.getElementById('cascade-canvas');
|
||
const ctx = canvas.getContext('2d');
|
||
const width = canvas.width;
|
||
const height = canvas.height;
|
||
|
||
// Clear canvas
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
if (cascadeConfig.mulden.length === 0 || cascadeConfig.totalLength === 0) return;
|
||
|
||
// Dark mode check
|
||
const isDarkMode = document.body.classList.contains('dark-mode');
|
||
if (isDarkMode) {
|
||
ctx.fillStyle = '#2a2a2a';
|
||
ctx.fillRect(0, 0, width, height);
|
||
}
|
||
|
||
// Skalierung
|
||
const scale = (width - 60) / cascadeConfig.totalLength;
|
||
const baseY = height * 0.7;
|
||
const muldenHeight = 80;
|
||
|
||
let currentX = 30;
|
||
|
||
// Gesamtlängen-Linie
|
||
ctx.strokeStyle = isDarkMode ? '#666' : '#ccc';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(30, baseY + 100);
|
||
ctx.lineTo(30 + cascadeConfig.totalLength * scale, baseY + 100);
|
||
ctx.stroke();
|
||
|
||
// Gesamtlängen-Beschriftung
|
||
ctx.fillStyle = isDarkMode ? '#e0e0e0' : '#333';
|
||
ctx.font = '12px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(`Gesamtlänge: ${cascadeConfig.totalLength.toFixed(2)} m`, width / 2, baseY + 120);
|
||
|
||
// Zeichne Anfangsbankett
|
||
if (cascadeConfig.bankettStart) {
|
||
ctx.fillStyle = '#ffc107';
|
||
ctx.fillRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
ctx.strokeStyle = '#e0a800';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
|
||
ctx.fillStyle = isDarkMode ? '#e0e0e0' : '#333';
|
||
ctx.font = '10px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('B', currentX + (cascadeConfig.bankettWidth * scale / 2), baseY - 25);
|
||
ctx.fillText(`${cascadeConfig.bankettWidth}m`, currentX + (cascadeConfig.bankettWidth * scale / 2), baseY - 5);
|
||
currentX += cascadeConfig.bankettWidth * scale;
|
||
}
|
||
|
||
// Zeichne Mulden
|
||
cascadeConfig.mulden.forEach((mulde, index) => {
|
||
const muldenLength = getMuldenLength(mulde.type);
|
||
const muldenWidth = muldenLength * scale;
|
||
|
||
// Muldenfarbe basierend auf Typ
|
||
const colors = {
|
||
'AG': '#e3f2fd',
|
||
'S2F': '#fff3cd',
|
||
'K1': '#d4edda',
|
||
'K2': '#f8d7da'
|
||
};
|
||
|
||
ctx.fillStyle = colors[mulde.type] || '#e3f2fd';
|
||
ctx.strokeStyle = '#007acc';
|
||
ctx.lineWidth = 2;
|
||
|
||
// Zeichne Muldenform
|
||
ctx.beginPath();
|
||
ctx.moveTo(currentX, baseY);
|
||
|
||
switch(mulde.type) {
|
||
case 'AG': // Durchgehend geneigt
|
||
ctx.lineTo(currentX + muldenWidth * 0.33, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth * 0.67, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY);
|
||
break;
|
||
case 'S2F': // Beidseitig vertikal
|
||
ctx.lineTo(currentX, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY);
|
||
break;
|
||
case 'K1': // Geneigt→vertikal
|
||
ctx.lineTo(currentX + muldenWidth * 0.5, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY);
|
||
break;
|
||
case 'K2': // Vertikal→geneigt
|
||
ctx.lineTo(currentX, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth * 0.5, baseY + muldenHeight);
|
||
ctx.lineTo(currentX + muldenWidth, baseY);
|
||
break;
|
||
}
|
||
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
ctx.stroke();
|
||
|
||
// Muldenbeschriftung
|
||
ctx.fillStyle = isDarkMode ? '#333' : '#333';
|
||
ctx.font = 'bold 14px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(`M${index + 1}`, currentX + muldenWidth / 2, baseY + muldenHeight / 2 - 10);
|
||
ctx.font = '11px Arial';
|
||
ctx.fillText(`${mulde.type}`, currentX + muldenWidth / 2, baseY + muldenHeight / 2 + 5);
|
||
ctx.fillText(`${muldenLength.toFixed(2)}m`, currentX + muldenWidth / 2, baseY + muldenHeight / 2 + 20);
|
||
|
||
currentX += muldenWidth;
|
||
|
||
// Zwischenbankett
|
||
if (mulde.bankettAfter && index < cascadeConfig.mulden.length - 1) {
|
||
ctx.fillStyle = '#ffc107';
|
||
ctx.fillRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
ctx.strokeStyle = '#e0a800';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
|
||
ctx.fillStyle = isDarkMode ? '#e0e0e0' : '#333';
|
||
ctx.font = '10px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(`B${index + 1}`, currentX + (cascadeConfig.bankettWidth * scale / 2), baseY - 25);
|
||
|
||
currentX += cascadeConfig.bankettWidth * scale;
|
||
}
|
||
});
|
||
|
||
// Zeichne Endbankett
|
||
if (cascadeConfig.bankettEnd) {
|
||
ctx.fillStyle = '#ffc107';
|
||
ctx.fillRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
ctx.strokeStyle = '#e0a800';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(currentX, baseY - 20, cascadeConfig.bankettWidth * scale, 20);
|
||
|
||
ctx.fillStyle = isDarkMode ? '#e0e0e0' : '#333';
|
||
ctx.font = '10px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('B', currentX + (cascadeConfig.bankettWidth * scale / 2), baseY - 25);
|
||
ctx.fillText(`${cascadeConfig.bankettWidth}m`, currentX + (cascadeConfig.bankettWidth * scale / 2), baseY - 5);
|
||
}
|
||
}
|
||
|
||
function updateCascadeSummary() {
|
||
const summaryDiv = document.getElementById('cascade-summary');
|
||
const contentDiv = document.getElementById('cascade-summary-content');
|
||
|
||
if (cascadeConfig.mulden.length === 0) {
|
||
summaryDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
// Berechne Gesamtlängen
|
||
let totalMuldenLength = 0;
|
||
let totalBankettLength = 0;
|
||
let bankettCount = 0;
|
||
|
||
// Anfangsbankett
|
||
if (cascadeConfig.bankettStart) {
|
||
totalBankettLength += cascadeConfig.bankettWidth;
|
||
bankettCount++;
|
||
}
|
||
|
||
// Mulden und Zwischenbankette
|
||
const muldenByType = { AG: 0, S2F: 0, K1: 0, K2: 0 };
|
||
cascadeConfig.mulden.forEach((mulde, index) => {
|
||
const length = getMuldenLength(mulde.type);
|
||
totalMuldenLength += length;
|
||
muldenByType[mulde.type]++;
|
||
|
||
if (mulde.bankettAfter && index < cascadeConfig.mulden.length - 1) {
|
||
totalBankettLength += cascadeConfig.bankettWidth;
|
||
bankettCount++;
|
||
}
|
||
});
|
||
|
||
// Endbankett
|
||
if (cascadeConfig.bankettEnd) {
|
||
totalBankettLength += cascadeConfig.bankettWidth;
|
||
bankettCount++;
|
||
}
|
||
|
||
const totalUsed = totalMuldenLength + totalBankettLength;
|
||
const restLength = cascadeConfig.totalLength - totalUsed;
|
||
|
||
// HTML generieren
|
||
let html = '<table style="width: 100%; border-collapse: collapse;">';
|
||
html += '<tr><th style="text-align: left; padding: 5px; border-bottom: 2px solid #007acc;">Element</th>';
|
||
html += '<th style="text-align: center; padding: 5px; border-bottom: 2px solid #007acc;">Anzahl</th>';
|
||
html += '<th style="text-align: right; padding: 5px; border-bottom: 2px solid #007acc;">Länge</th></tr>';
|
||
|
||
// Mulden nach Typ
|
||
Object.entries(muldenByType).forEach(([type, count]) => {
|
||
if (count > 0) {
|
||
const unitLength = getMuldenLength(type);
|
||
html += `<tr>
|
||
<td style="padding: 5px;">Mulden Typ ${type}</td>
|
||
<td style="text-align: center; padding: 5px;">${count}</td>
|
||
<td style="text-align: right; padding: 5px;">${count} × ${unitLength.toFixed(3)} = ${(count * unitLength).toFixed(3)} m</td>
|
||
</tr>`;
|
||
}
|
||
});
|
||
|
||
// Bankette
|
||
if (bankettCount > 0) {
|
||
html += `<tr>
|
||
<td style="padding: 5px;">Bankette</td>
|
||
<td style="text-align: center; padding: 5px;">${bankettCount}</td>
|
||
<td style="text-align: right; padding: 5px;">${bankettCount} × ${cascadeConfig.bankettWidth.toFixed(2)} = ${totalBankettLength.toFixed(2)} m</td>
|
||
</tr>`;
|
||
}
|
||
|
||
// Summen
|
||
html += '<tr style="border-top: 2px solid #007acc; font-weight: bold;">';
|
||
html += `<td style="padding: 5px;">Gesamt verwendet</td>`;
|
||
html += `<td style="text-align: center; padding: 5px;">${cascadeConfig.mulden.length + bankettCount}</td>`;
|
||
html += `<td style="text-align: right; padding: 5px;">${totalUsed.toFixed(3)} m</td>`;
|
||
html += '</tr>';
|
||
|
||
html += `<tr style="color: ${restLength < 0 ? '#dc3545' : '#28a745'}; font-weight: bold;">
|
||
<td style="padding: 5px;">Restlänge</td>
|
||
<td style="text-align: center; padding: 5px;">-</td>
|
||
<td style="text-align: right; padding: 5px;">${restLength.toFixed(3)} m</td>
|
||
</tr>`;
|
||
|
||
html += '</table>';
|
||
|
||
if (restLength < -0.001) {
|
||
html += '<p style="color: #dc3545; margin-top: 10px;">⚠️ Konfiguration überschreitet Gesamtlänge!</p>';
|
||
}
|
||
|
||
contentDiv.innerHTML = html;
|
||
summaryDiv.style.display = 'block';
|
||
|
||
// Vorschlag zur Füllung der Restlänge berechnen und anzeigen
|
||
calculateSuggestion(restLength);
|
||
// Vorschlag zur Verteilung der Differenz berechnen
|
||
calculateDistributeSuggestion(restLength);
|
||
}
|
||
|
||
function calculateSuggestion(restLength) {
|
||
const suggestionDiv = document.getElementById('cascade-suggestion');
|
||
if (restLength <= 0.001 || !currentResults || !currentResults.points.A) {
|
||
suggestionDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const defaultType = document.getElementById('cascade-default-type').value;
|
||
const targetLength = restLength;
|
||
|
||
const res = currentResults;
|
||
const p = res.points;
|
||
const L_AS2 = res.innerTriangle.f;
|
||
|
||
// Isoliere die aktuelle FG-Länge (approximativ)
|
||
const s2f_current = getMuldenLength('S2F');
|
||
const k2_current = getMuldenLength('K2');
|
||
const L_FG_current = (k2_current > s2f_current) ? (k2_current - s2f_current) : 0;
|
||
|
||
let s2f_new;
|
||
switch(defaultType) {
|
||
case 'AG':
|
||
s2f_new = targetLength - L_AS2 - L_FG_current;
|
||
break;
|
||
case 'S2F':
|
||
s2f_new = targetLength;
|
||
break;
|
||
case 'K1':
|
||
s2f_new = targetLength - L_AS2;
|
||
break;
|
||
case 'K2':
|
||
s2f_new = targetLength - L_FG_current;
|
||
break;
|
||
default:
|
||
suggestionDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
if (s2f_new <= 0) {
|
||
suggestionDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
// Berechne den entsprechenden EF-Wert für den neuen S2F-Wert
|
||
const m_as2 = (p.S2.z - p.A.z) / (p.S2.x - p.A.x);
|
||
const dx = s2f_new / Math.sqrt(1 + m_as2**2);
|
||
const Fx_new = p.S2.x + dx;
|
||
const Fz_new = m_as2 * Fx_new;
|
||
const ef_new = Math.abs(Fz_new - p.B.z);
|
||
|
||
// Zeige den Vorschlag an
|
||
document.getElementById('suggestion-text').textContent = `Um die Restlänge von ${restLength.toFixed(3)} m mit einer weiteren Mulde (Typ ${defaultType}) zu füllen, könnten Sie einen der folgenden Werte verwenden:`;
|
||
document.getElementById('suggestion-s2f').textContent = `${s2f_new.toFixed(3)} m`;
|
||
document.getElementById('suggestion-ef').textContent = `${ef_new.toFixed(3)} m`;
|
||
|
||
// Speichere die Werte für die "Anwenden"-Buttons
|
||
suggestionDiv.dataset.s2f = s2f_new.toFixed(3);
|
||
suggestionDiv.dataset.ef = ef_new.toFixed(3);
|
||
|
||
suggestionDiv.style.display = 'block';
|
||
}
|
||
|
||
function applySuggestion(type) {
|
||
const suggestionDiv = document.getElementById('cascade-suggestion');
|
||
if (type === 's2f') {
|
||
const s2f_val = suggestionDiv.dataset.s2f;
|
||
const s2f_input = document.getElementById('trapezoid-s2f');
|
||
s2f_input.value = s2f_val;
|
||
handleTrapezoidInput(s2f_input.id);
|
||
} else if (type === 'ef') {
|
||
const ef_val = suggestionDiv.dataset.ef;
|
||
const ef_input = document.getElementById('trapezoid-ef');
|
||
ef_input.value = ef_val;
|
||
handleTrapezoidInput(ef_input.id);
|
||
}
|
||
calculate();
|
||
}
|
||
|
||
function calculateDistributeSuggestion(restLength) {
|
||
const suggestionDiv = document.getElementById('cascade-distribute-suggestion');
|
||
const muldenCount = cascadeConfig.mulden.length;
|
||
|
||
if (Math.abs(restLength) < 0.001 || muldenCount === 0 || !currentResults) {
|
||
suggestionDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const deltaPerTrough = restLength / muldenCount;
|
||
const s2f_current = getMuldenLength('S2F');
|
||
const s2f_new = s2f_current + deltaPerTrough;
|
||
|
||
if (s2f_new <= 0) {
|
||
suggestionDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
// Berechne den entsprechenden EF-Wert für den neuen S2F-Wert
|
||
const p = currentResults.points;
|
||
const m_as2 = (p.S2.z - p.A.z) / (p.S2.x - p.A.x);
|
||
const dx = s2f_new / Math.sqrt(1 + m_as2**2);
|
||
const Fx_new = p.S2.x + dx;
|
||
const Fz_new = m_as2 * Fx_new;
|
||
const ef_new = Math.abs(Fz_new - p.B.z);
|
||
|
||
document.getElementById('distribute-suggestion-text').textContent = `Um die Differenz von ${restLength.toFixed(3)} m auf die ${muldenCount} vorhandenen Mulden zu verteilen, muss S2F angepasst werden:`;
|
||
document.getElementById('distribute-suggestion-s2f').textContent = `${s2f_new.toFixed(3)} m`;
|
||
document.getElementById('distribute-suggestion-ef').textContent = `${ef_new.toFixed(3)} m`;
|
||
|
||
suggestionDiv.dataset.s2f = s2f_new.toFixed(3);
|
||
suggestionDiv.style.display = 'block';
|
||
}
|
||
|
||
function applyDistributeSuggestion(type) {
|
||
const s2f_val = document.getElementById('cascade-distribute-suggestion').dataset.s2f;
|
||
const s2f_input = document.getElementById('trapezoid-s2f');
|
||
s2f_input.value = s2f_val;
|
||
handleTrapezoidInput(s2f_input.id);
|
||
calculate();
|
||
}
|
||
|
||
function exportCascadeConfig() {
|
||
if (cascadeConfig.mulden.length === 0) {
|
||
alert('Keine Mulden zum Exportieren vorhanden');
|
||
return;
|
||
}
|
||
|
||
let text = 'KASKADEN-KONFIGURATION\n';
|
||
text += '=' .repeat(50) + '\n\n';
|
||
text += `Datum: ${new Date().toLocaleString('de-DE')}\n`;
|
||
text += `Gesamtlänge: ${cascadeConfig.totalLength} m\n`;
|
||
text += `Bankettbreite: ${cascadeConfig.bankettWidth} m\n\n`;
|
||
|
||
text += 'ELEMENTE:\n';
|
||
text += '-'.repeat(30) + '\n';
|
||
|
||
let position = 0;
|
||
|
||
if (cascadeConfig.bankettStart) {
|
||
text += `Position ${position.toFixed(2)}m: Anfangsbankett (${cascadeConfig.bankettWidth}m)\n`;
|
||
position += cascadeConfig.bankettWidth;
|
||
}
|
||
|
||
cascadeConfig.mulden.forEach((mulde, index) => {
|
||
const length = getMuldenLength(mulde.type);
|
||
text += `Position ${position.toFixed(2)}m: Mulde ${index + 1} - Typ ${mulde.type} (${length.toFixed(3)}m)\n`;
|
||
position += length;
|
||
|
||
if (mulde.bankettAfter && index < cascadeConfig.mulden.length - 1) {
|
||
text += `Position ${position.toFixed(2)}m: Zwischenbankett (${cascadeConfig.bankettWidth}m)\n`;
|
||
position += cascadeConfig.bankettWidth;
|
||
}
|
||
});
|
||
|
||
if (cascadeConfig.bankettEnd) {
|
||
text += `Position ${position.toFixed(2)}m: Endbankett (${cascadeConfig.bankettWidth}m)\n`;
|
||
}
|
||
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
alert('Konfiguration in Zwischenablage kopiert!');
|
||
});
|
||
}
|
||
|
||
// Initialisierung
|
||
window.onload = () => {
|
||
// Dark Mode aus localStorage laden
|
||
if (localStorage.getItem('darkMode') === 'true') {
|
||
document.body.classList.add('dark-mode');
|
||
}
|
||
|
||
document.getElementById('beta-ratio-right').value = DEFAULTS.beta_ratio_right;
|
||
convertBeta('beta-ratio');
|
||
document.getElementById('bs2').value = DEFAULTS.bs2;
|
||
document.getElementById('zeta-percent').value = DEFAULTS.zeta_percent;
|
||
convertZeta('zeta-percent');
|
||
document.getElementById('trapezoid-ef').value = DEFAULTS.trapezoid_ef;
|
||
document.getElementById('angle-e-degrees').value = DEFAULTS.angle_e_degrees;
|
||
convertAngleE('angle-e-degrees');
|
||
|
||
// Setze EF als initial "Chef"
|
||
lastModifiedInput = 'trapezoid-ef';
|
||
|
||
calculate();
|
||
|
||
// Initialisiere Kaskadenrechner
|
||
updateCascadeLengths();
|
||
updateCascadeConfigDisplay();
|
||
};
|
||
</script>
|
||
|
||
|
||
<footer style="text-align:center;padding:1rem;margin-top:2rem;border-top:1px solid #e5e7eb;font-size:0.85rem;color:#6b7280;">
|
||
<a href="#" onclick="openImpressum();return false;" style="color:#6b7280;text-decoration:none;">Impressum</a>
|
||
<span style="color:#d1d5db;margin:0 0.5rem;">|</span>
|
||
<a href="#" onclick="openDatenschutz();return false;" style="color:#6b7280;text-decoration:none;">Datenschutz</a>
|
||
</footer>
|
||
|
||
|
||
<!-- IMPRESSUM MODAL -->
|
||
<div class="legal-modal" id="impressumModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Impressum</h2>
|
||
<button onclick="closeImpressum()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||
</div>
|
||
<iframe src="/legal/impressum.html" style="flex:1;width:100%;border:none;"></iframe>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DATENSCHUTZ MODAL -->
|
||
<div class="legal-modal" id="datenschutzModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
|
||
<div style="background:white;width:90%;max-width:900px;height:90vh;border-radius:12px;margin:20px;overflow:hidden;display:flex;flex-direction:column;">
|
||
<div style="padding:0.75rem 1.5rem;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
|
||
<h2 style="margin:0;font-size:1.25rem;color:#1f2937;">Datenschutz</h2>
|
||
<button onclick="closeDatenschutz()" style="background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6b7280;line-height:1;">×</button>
|
||
</div>
|
||
<iframe src="/legal/datenschutz.html" style="flex:1;width:100%;border:none;"></iframe>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function openImpressum(){document.getElementById("impressumModal").style.display="flex";}
|
||
function closeImpressum(){document.getElementById("impressumModal").style.display="none";}
|
||
function openDatenschutz(){document.getElementById("datenschutzModal").style.display="flex";}
|
||
function closeDatenschutz(){document.getElementById("datenschutzModal").style.display="none";}
|
||
document.addEventListener("keydown",function(e){if(e.key==="Escape"){closeImpressum();closeDatenschutz();}});
|
||
["impressumModal","datenschutzModal"].forEach(function(id){
|
||
var el=document.getElementById(id);
|
||
if(el)el.addEventListener("click",function(e){if(e.target===this)this.style.display="none";});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|