Kodak419/CircularMovement
0
1<!DOCTYPE html>2<html lang="he" dir="rtl">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">6 <title>מעבדה פיזיקלית: תנועה סיבובית חיכוך ככוח רדיאלי</title>7 <style>8 :root { --primary: #00d4ff; --bg: #121212; --panel: #1e1e1e; --accent: #ff0055; --safe: #00ff88; }9 body { 10 font-family: 'Segoe UI', system-ui, sans-serif; 11 margin: 0; background: var(--bg); color: white; 12 display: flex; flex-direction: column; height: 100vh; overflow: hidden;13 direction: rtl;14 }15 @media (min-width: 768px) { body { flex-direction: row; } }16 #controls { 17 background: var(--panel); padding: 15px; box-sizing: border-box;18 z-index: 10; box-shadow: -5px 0 15px rgba(0,0,0,0.5);19 overflow-y: auto; width: 100%; display: flex; flex-direction: column;20 }21 @media (min-width: 768px) { #controls { width: 380px; height: 100vh; } }22 #canvas-container { flex-grow: 1; position: relative; background: #222; overflow: hidden; display: flex; align-items: center; justify-content: center; }23 canvas#simCanvas { display: block; width: 100%; height: 100%; }24 .param-group { margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 8px; }25 label { display: block; font-size: 0.8rem; color: var(--primary); font-weight: bold; margin-bottom: 4px; }26 input[type="range"] { width: 100%; height: 8px; accent-color: var(--primary); cursor: pointer; }27 select { width: 100%; padding: 8px; background: #333; color: white; border: none; border-radius: 5px; font-size: 14px; }28 .stats-panel { background: rgba(0,0,0,0.4); padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 0.85rem; border: 1px solid #444; }29 .stat-line { display: flex; justify-content: space-between; margin: 3px 0; }30 .stat-val { color: var(--primary); font-family: monospace; direction: ltr; }31 #graph-container { background: #000; height: 100px; border-radius: 5px; margin-top: 10px; border: 1px solid #444; }32 canvas#graphCanvas { width: 100%; height: 100%; }33 .status { padding: 10px; border-radius: 6px; text-align: center; font-weight: bold; margin-top: 10px; font-size: 0.9rem; }34 .stable { background: rgba(0, 255, 136, 0.1); color: var(--safe); border: 1px solid var(--safe); }35 .slipping { background: rgba(255, 0, 85, 0.1); color: var(--accent); border: 1px solid var(--accent); animation: blink 0.4s infinite; }36 @keyframes blink { 50% { opacity: 0.5; } }37 button { width: 100%; padding: 12px; background: var(--primary); border: none; color: #000; border-radius: 5px; font-weight: bold; cursor: pointer; margin-top: 5px; transition: 0.2s; }38 button:active { transform: scale(0.98); background: #fff; }39 .value-label { float: left; color: #aaa; font-size: 0.75rem; }40 </style>41</head>42<body>43 44<div id="controls">45 <h3 style="margin: 0 0 10px 0; color: var(--primary); text-align: center;">מערכת ייצוב אקטיביתמעבדה פיזיקלית: תנועה סיבובית חיכוך ככוח רדיאלי</h3>46 47 <div class="param-group">48 <label>דגם רכב</label>49 <select id="carType">50 <option value="sedan">🚗 רכב משפחתי</option>51 <option value="truck">🚛 משאית כבדה</option>52 <option value="race">🏎️ רכב מרוץ F1</option>53 </select>54 </div>55 56 <div class="param-group">57 <label>מהירות (v): <span class="value-label" id="vDisp">60</span> מ'/שנ'</label>58 <input type="range" id="vel" min="5" max="250" value="60">59 </div>60 61 <div class="param-group">62 <label>רדיוס (r): <span class="value-label" id="rDisp">150</span> מטר</label>63 <input type="range" id="rad" min="40" max="600" value="150">64 </div>65 66 <div class="param-group">67 <label>מסה (m): <span class="value-label" id="mDisp">1500</span> ק"ג</label>68 <input type="range" id="mass" min="500" max="15000" step="100" value="1500">69 </div>70 71 <div class="param-group">72 <label>חיכוך (μ): <span class="value-label" id="fDisp">0.7</span></label>73 <input type="range" id="fric" min="0.05" max="2.0" step="0.01" value="0.7">74 </div>75 76 <div class="stats-panel">77 <div class="stat-line"><span>מהירות זוויתית (ω):</span> <span class="stat-val" id="angVelVal">0 rad/s</span></div>78 <div class="stat-line"><span>מיקום (θ):</span> <span class="stat-val" id="angPosVal">0.00 rad</span></div>79 <div class="stat-line"><span>משקל (W):</span> <span class="stat-val" id="weightVal">0 N</span></div>80 </div>81 82 <div id="graph-container">83 <canvas id="graphCanvas"></canvas>84 </div>85 86 <button id="resetMode">ייצב והחזר לכביש (v & μ)</button>87 <div id="status" class="status stable">אחיזה תקינה</div>88</div>89 90<div id="canvas-container">91 <canvas id="simCanvas"></canvas>92</div>93 94<script>95 const simCanvas = document.getElementById('simCanvas');96 const simCtx = simCanvas.getContext('2d');97 const graphCanvas = document.getElementById('graphCanvas');98 const graphCtx = graphCanvas.getContext('2d');99 const container = document.getElementById('canvas-container');100 101 const inputs = {102 vel: document.getElementById('vel'),103 rad: document.getElementById('rad'),104 mass: document.getElementById('mass'),105 fric: document.getElementById('fric'),106 type: document.getElementById('carType')107 };108 109 let carAngle = 0;110 let driftOffset = 0;111 let forceHistory = [];112 const G = 9.8;113 114 function resize() {115 simCanvas.width = container.clientWidth;116 simCanvas.height = container.clientHeight;117 graphCanvas.width = graphCanvas.offsetWidth;118 graphCanvas.height = graphCanvas.offsetHeight;119 }120 window.addEventListener('resize', resize);121 resize();122 123 // לוגיקת הכפתור החדשה: משנה גם מהירות וגם חיכוך124 document.getElementById('resetMode').onclick = () => {125 const r = parseFloat(inputs.rad.value);126 127 // 1. קביעת חיכוך גבוה ובטוח128 const targetMu = 0.85;129 inputs.fric.value = targetMu;130 131 // 2. חישוב מהירות מקסימלית מותרת: v = sqrt(mu * g * r)132 // אנחנו לוקחים 90% מהמהירות המקסימלית לביטחון133 const maxSafeV = Math.sqrt(targetMu * G * r) * 0.9;134 135 // 3. עדכון הסליידר של המהירות136 inputs.vel.value = Math.min(maxSafeV, 250).toFixed(0);137 138 // 4. איפוס פיזיקלי של המיקום139 driftOffset = 0;140 };141 142 function drawCar(type, x, y, angle) {143 simCtx.save();144 simCtx.translate(x, y);145 simCtx.rotate(angle); 146 if (type === 'sedan') {147 simCtx.fillStyle = "#00d4ff"; simCtx.beginPath(); simCtx.roundRect(-8, -14, 16, 28, 4); simCtx.fill();148 simCtx.fillStyle = "rgba(255,255,255,0.4)"; simCtx.fillRect(-6, 5, 12, 5); 149 } else if (type === 'truck') {150 simCtx.fillStyle = "#eee"; simCtx.fillRect(-10, -25, 20, 35);151 simCtx.fillStyle = "#f39c12"; simCtx.fillRect(-11, 10, 22, 12);152 } else if (type === 'race') {153 simCtx.fillStyle = "#ff0055"; simCtx.beginPath(); simCtx.moveTo(0, 18); simCtx.lineTo(-12, -8); simCtx.lineTo(12, -8); simCtx.fill();154 simCtx.fillStyle = "#000"; simCtx.fillRect(-14, -10, 28, 4);155 }156 simCtx.restore();157 }158 159 function updateGraph(req, avail) {160 forceHistory.push({req, avail});161 if (forceHistory.length > 100) forceHistory.shift();162 graphCtx.clearRect(0, 0, graphCanvas.width, graphCanvas.height);163 const maxV = Math.max(...forceHistory.map(d => Math.max(d.req, d.avail)), 1000);164 const sY = graphCanvas.height / (maxV * 1.2);165 const sX = graphCanvas.width / 100;166 graphCtx.strokeStyle = '#00ff88'; graphCtx.beginPath();167 forceHistory.forEach((d, i) => { const x = i*sX; const y = graphCanvas.height - d.avail*sY; i===0?graphCtx.moveTo(x,y):graphCtx.lineTo(x,y); });168 graphCtx.stroke();169 graphCtx.strokeStyle = '#ff0055'; graphCtx.beginPath();170 forceHistory.forEach((d, i) => { const x = i*sX; const y = graphCanvas.height - d.req*sY; i===0?graphCtx.moveTo(x,y):graphCtx.lineTo(x,y); });171 graphCtx.stroke();172 }173 174 function draw() {175 simCtx.clearRect(0, 0, simCanvas.width, simCanvas.height);176 const cx = simCanvas.width / 2;177 const cy = simCanvas.height / 2;178 179 const v = parseFloat(inputs.vel.value);180 const r = parseFloat(inputs.rad.value);181 const m = parseFloat(inputs.mass.value);182 const mu = parseFloat(inputs.fric.value);183 184 const weight = m * G;185 const centripetalReq = (m * v * v) / r;186 const frictionAvail = mu * weight;187 const angVel = v / r;188 189 document.getElementById('vDisp').innerText = v;190 document.getElementById('rDisp').innerText = r;191 document.getElementById('mDisp').innerText = m;192 document.getElementById('fDisp').innerText = mu.toFixed(2);193 document.getElementById('weightVal').innerText = Math.round(weight).toLocaleString() + " N";194 document.getElementById('angVelVal').innerText = angVel.toFixed(3);195 const displayRad = (carAngle % (Math.PI * 2) + (Math.PI * 2)) % (Math.PI * 2);196 document.getElementById('angPosVal').innerText = displayRad.toFixed(2) + " rad";197 198 updateGraph(centripetalReq, frictionAvail);199 200 const isSlipping = centripetalReq > frictionAvail;201 const scale = Math.min(simCanvas.width, simCanvas.height) * 0.42;202 const visualR = (r / 600) * scale;203 204 simCtx.beginPath();205 simCtx.arc(cx, cy, visualR, 0, Math.PI * 2);206 simCtx.strokeStyle = '#333'; simCtx.lineWidth = 50; simCtx.stroke();207 simCtx.strokeStyle = '#f1c40f'; simCtx.lineWidth = 2; simCtx.setLineDash([20, 20]); simCtx.stroke();208 simCtx.setLineDash([]);209 210 if (isSlipping) {211 document.getElementById('status').innerText = "אזהרה: איבוד אחיזה!";212 document.getElementById('status').className = "status slipping";213 driftOffset += (centripetalReq / frictionAvail) * 0.6;214 } else {215 document.getElementById('status').innerText = "אחיזה תקינה";216 document.getElementById('status').className = "status stable";217 driftOffset *= 0.9;218 }219 220 carAngle += angVel * 0.035; 221 const carX = cx + (visualR + driftOffset) * Math.cos(carAngle);222 const carY = cy + (visualR + driftOffset) * Math.sin(carAngle);223 drawCar(inputs.type.value, carX, carY, carAngle + Math.PI/2);224 225 requestAnimationFrame(draw);226 }227 draw();228</script>229</body>230</html>