Some checks failed
Build & Deploy / Validation, build et push (push) Failing after 5m8s
233 lines
10 KiB
JavaScript
233 lines
10 KiB
JavaScript
"use strict";
|
|
(function(){
|
|
const root = document.querySelector(".aero-calc");
|
|
if (!root) {
|
|
console.error('calculateur legacy: .aero-calc container not found');
|
|
return;
|
|
}
|
|
const $ = id => {
|
|
const el = root.querySelector("#"+id);
|
|
// silencieusement retourner null si l'élément n'existe pas
|
|
return el;
|
|
};
|
|
|
|
const C = {};
|
|
function readConsts(){
|
|
try {
|
|
const ct = $("aero-ct"); if(ct) C.CT=parseFloat(ct.value); else console.warn('aero-ct not found');
|
|
const sair = $("aero-sair"); if(sair) C.sAir=parseFloat(sair.value); else console.warn('aero-sair not found');
|
|
const g = $("aero-g"); if(g) C.g=parseFloat(g.value); else console.warn('aero-g not found');
|
|
const sea = $("aero-sea"); if(sea) C.sea=parseFloat(sea.value); else console.warn('aero-sea not found');
|
|
const k = $("aero-k"); if(k) C.k=parseFloat(k.value); else console.warn('aero-k not found');
|
|
const pmax = $("aero-pmax"); if(pmax) C.pMax=parseFloat(pmax.value); else console.warn('aero-pmax not found');
|
|
const ytop = $("aero-ytop"); if(ytop) C.yTop=parseFloat(ytop.value); else console.warn('aero-ytop not found');
|
|
const ybot = $("aero-ybot"); if(ybot) C.yBot=parseFloat(ybot.value); else console.warn('aero-ybot not found');
|
|
} catch(e) { console.error('readConsts error:', e); }
|
|
}
|
|
|
|
function pressure(y){ let p=Math.exp(C.k*(y-C.sea)); if(p>C.pMax)p=C.pMax; return p<0?0:p; }
|
|
// Le mod echantillonne la pression au CENTRE du bloc : getBlockPos().getCenter() -> y+0.5
|
|
const Y_OFFSET = 0.5;
|
|
const propThrust = (P,N)=>C.CT*Math.pow(Math.max(P,0),1.5)*N;
|
|
const balloonForce = (V,y)=>Math.max(V,0)*C.sAir*C.g*pressure(y);
|
|
const balloonLift = (V,y)=>Math.max(V,0)*C.sAir*pressure(y);
|
|
function maxAltitude(V,m){
|
|
if(V<=0||m<=0) return {y:C.yTop,status:"ceiling"};
|
|
const t=m/(V*C.sAir);
|
|
if(t>C.pMax) return {y:C.yBot,status:"grounded"};
|
|
let y=C.sea+Math.log(t)/C.k;
|
|
if(y>=C.yTop) return {y:C.yTop,status:"ceiling"};
|
|
if(y<=C.yBot) return {y:C.yBot,status:"bottom"};
|
|
return {y,status:"ok"};
|
|
}
|
|
const requiredVolume=(m,y)=>m/(C.sAir*pressure(y));
|
|
|
|
const nf=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:0});
|
|
const nf2=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:2});
|
|
const nf3=new Intl.NumberFormat("fr-FR",{maximumFractionDigits:3});
|
|
function big(x){ if(!isFinite(x)) return "n/a"; return Math.abs(x)>=1000?nf.format(x):nf2.format(x); }
|
|
|
|
function updateProp(){
|
|
try {
|
|
const sails = $("aero-sails"); const P = sails ? parseFloat(sails.value)||0 : 0;
|
|
const rpm = $("aero-rpm"); const N = rpm ? parseFloat(rpm.value)||0 : 0;
|
|
const palt = $("aero-palt"); const y = palt ? parseFloat(palt.value)||0 : 0;
|
|
const T=propThrust(P,N);
|
|
const thrust = $("aero-othrust"); if(thrust) thrust.innerHTML=big(Math.abs(T))+' <small>pN</small>';
|
|
const thrustapp = $("aero-othrustapp"); if(thrustapp) thrustapp.innerHTML=big(Math.abs(T)*pressure(y+Y_OFFSET))+' <small>pN</small>';
|
|
} catch(e) { console.error('updateProp error:', e); }
|
|
}
|
|
function updateBalloon(){
|
|
try {
|
|
const vol = $("aero-vol"); const V = vol ? parseFloat(vol.value)||0 : 0;
|
|
const balt = $("aero-balt"); const y = balt ? parseFloat(balt.value)||0 : 0;
|
|
const force = $("aero-oforce"); if(force) force.innerHTML=big(balloonForce(V,y))+' <small>pN</small>';
|
|
const lift = $("aero-olift"); if(lift) lift.innerHTML=big(balloonLift(V,y))+' <small>kpg</small>';
|
|
const press = $("aero-opress"); if(press) press.textContent=nf3.format(pressure(y));
|
|
} catch(e) { console.error('updateBalloon error:', e); }
|
|
}
|
|
function updateVolume(){
|
|
try {
|
|
const vmass = $("aero-vmass"); const m = vmass ? parseFloat(vmass.value)||0 : 0;
|
|
const valt = $("aero-valt"); const y = valt ? parseFloat(valt.value)||0 : 0;
|
|
const V=requiredVolume(m,y);
|
|
const volreq = $("aero-ovolreq"); if(volreq) volreq.innerHTML=big(V)+' <small>m³</small>';
|
|
const cube = $("aero-ocube"); if(cube) cube.textContent=(isFinite(V)&&V>0)
|
|
? "≈ cube de "+nf2.format(Math.cbrt(V))+" blocs de côté · "+nf.format(Math.ceil(V))+" blocs d'air"
|
|
: "";
|
|
const warn = $("aero-vwarn"); if(warn) warn.innerHTML=(pressure(y)<=0)
|
|
? "<b>Altitude hors atmosphère :</b> pression nulle, aucun volume fini ne suffit." : "";
|
|
} catch(e) { console.error('updateVolume error:', e); }
|
|
}
|
|
|
|
// ---- graphe ----
|
|
let mode="mass";
|
|
const canvas=$("aero-chart"), tip=$("aero-tip");
|
|
if (!canvas) {
|
|
console.error('calculateur legacy: canvas not found, graph disabled');
|
|
}
|
|
const ctx = canvas ? canvas.getContext("2d") : null;
|
|
let geom=null;
|
|
const tv=n=>getComputedStyle(root).getPropertyValue(n).trim()||"#888";
|
|
|
|
function niceStep(raw){ const p=Math.pow(10,Math.floor(Math.log10(raw))); const n=raw/p; return (n<1.5?1:n<3?2:n<7?5:10)*p; }
|
|
function abbr(v){ const a=Math.abs(v); if(a>=1e6)return nf2.format(v/1e6)+"M"; if(a>=1e3)return nf2.format(v/1e3)+"k"; return nf.format(v); }
|
|
|
|
function drawChart(){
|
|
if(!canvas||!ctx) { console.warn('calculateur legacy: graph not available'); return; }
|
|
const dpr=window.devicePixelRatio||1;
|
|
const cssW=canvas.parentElement.clientWidth;
|
|
const cssH=Math.max(260,Math.round(cssW*0.44));
|
|
canvas.style.height=cssH+"px";
|
|
canvas.width=Math.round(cssW*dpr); canvas.height=Math.round(cssH*dpr);
|
|
ctx.setTransform(dpr,0,0,dpr,0,0); ctx.clearRect(0,0,cssW,cssH);
|
|
|
|
const padL=54,padR=16,padT=14,padB=38, pw=cssW-padL-padR, ph=cssH-padT-padB;
|
|
const fg=tv("--aero-fg"), muted=tv("--aero-muted"), border=tv("--aero-border"), accent=tv("--aero-accent"), warn=tv("--aero-warn");
|
|
|
|
let xMin,xMax,xLabel,fixed;
|
|
if(mode==="mass"){
|
|
const fixvol = $("aero-fixvol");
|
|
fixed=Math.max(1, fixvol ? parseFloat(fixvol.value)||1 : 1);
|
|
xMin=1; xMax=Math.max(2,fixed*C.sAir*C.pMax*1.15); xLabel="poids m (kpg)";
|
|
} else {
|
|
const fixmass = $("aero-fixmass");
|
|
fixed=Math.max(1, fixmass ? parseFloat(fixmass.value)||1 : 1);
|
|
xMin=Math.max(1,fixed/(C.sAir*C.pMax)*0.5); xMax=Math.max(xMin*1.5,4*fixed/C.sAir); xLabel="volume V (m³)";
|
|
}
|
|
const yMin=C.yBot,yMax=C.yTop;
|
|
const X=x=>padL+(x-xMin)/(xMax-xMin)*pw;
|
|
const Y=y=>padT+(1-(y-yMin)/(yMax-yMin))*ph;
|
|
|
|
ctx.font='11px '+(getComputedStyle(root).fontFamily||"sans-serif");
|
|
// grille
|
|
ctx.strokeStyle=border; ctx.fillStyle=muted; ctx.lineWidth=1;
|
|
ctx.textBaseline="middle"; ctx.textAlign="right";
|
|
const yStep=niceStep((yMax-yMin)/6);
|
|
for(let yy=Math.ceil(yMin/yStep)*yStep; yy<=yMax; yy+=yStep){
|
|
const py=Y(yy); ctx.beginPath();ctx.moveTo(padL,py);ctx.lineTo(padL+pw,py);ctx.stroke();
|
|
ctx.fillText(String(yy),padL-7,py);
|
|
}
|
|
ctx.textAlign="center"; ctx.textBaseline="top";
|
|
const xStep=niceStep((xMax-xMin)/6);
|
|
for(let xx=Math.ceil(xMin/xStep)*xStep; xx<=xMax; xx+=xStep){
|
|
const px=X(xx); ctx.beginPath();ctx.moveTo(px,padT);ctx.lineTo(px,padT+ph);ctx.stroke();
|
|
ctx.fillText(abbr(xx),px,padT+ph+7);
|
|
}
|
|
ctx.fillStyle=muted; ctx.fillText(xLabel,padL+pw/2,padT+ph+22);
|
|
ctx.save();ctx.translate(13,padT+ph/2);ctx.rotate(-Math.PI/2);ctx.fillText("altitude max y",0,0);ctx.restore();
|
|
|
|
function refLine(yv,label){
|
|
if(yv<yMin||yv>yMax) return;
|
|
const py=Y(yv);
|
|
ctx.strokeStyle=muted; ctx.setLineDash([4,4]); ctx.lineWidth=1;
|
|
ctx.beginPath();ctx.moveTo(padL,py);ctx.lineTo(padL+pw,py);ctx.stroke(); ctx.setLineDash([]);
|
|
ctx.fillStyle=muted; ctx.textAlign="left"; ctx.textBaseline="bottom"; ctx.fillText(label,padL+5,py-2);
|
|
}
|
|
refLine(C.sea,"niveau mer "+C.sea);
|
|
refLine(C.yTop,"limite "+C.yTop);
|
|
|
|
// courbe
|
|
const steps=240,pts=[];
|
|
for(let i=0;i<=steps;i++){ const x=xMin+(xMax-xMin)*i/steps; const r=(mode==="mass")?maxAltitude(fixed,x):maxAltitude(x,fixed); pts.push({x,y:r.y,status:r.status}); }
|
|
// zone grounded
|
|
ctx.fillStyle=warn; ctx.globalAlpha=.10; let gs=null;
|
|
for(let i=0;i<=steps;i++){
|
|
const g=pts[i].status==="grounded";
|
|
if(g&&gs===null)gs=pts[i].x;
|
|
if((!g||i===steps)&&gs!==null){ ctx.fillRect(X(gs),padT,X(pts[i].x)-X(gs),ph); gs=null; }
|
|
}
|
|
ctx.globalAlpha=1;
|
|
ctx.lineWidth=2; ctx.strokeStyle=accent; ctx.beginPath(); let started=false;
|
|
for(const p of pts){ if(p.status==="grounded"){started=false;continue;} const px=X(p.x),py=Y(p.y); if(!started){ctx.moveTo(px,py);started=true;}else ctx.lineTo(px,py); }
|
|
ctx.stroke();
|
|
|
|
geom={padL,pw,padT,ph,xMin,xMax,X,Y,fixed};
|
|
}
|
|
|
|
if(canvas && tip) {
|
|
canvas.addEventListener("mousemove",e=>{
|
|
if(!geom)return; const rect=canvas.getBoundingClientRect(); const mx=e.clientX-rect.left;
|
|
if(mx<geom.padL||mx>geom.padL+geom.pw){tip.style.opacity=0;return;}
|
|
const x=geom.xMin+(mx-geom.padL)/geom.pw*(geom.xMax-geom.xMin);
|
|
const r=(mode==="mass")?maxAltitude(geom.fixed,x):maxAltitude(x,geom.fixed);
|
|
let txt = r.status==="grounded" ? abbr(x)+" : ne décolle pas"
|
|
: r.status==="ceiling" ? abbr(x)+" : plafond (y "+Math.round(r.y)+")"
|
|
: abbr(x)+" : y "+Math.round(r.y);
|
|
tip.textContent=txt; tip.style.left=geom.X(x)+"px"; tip.style.top=geom.Y(r.y)+"px"; tip.style.opacity=1;
|
|
});
|
|
canvas.addEventListener("mouseleave",()=>{tip.style.opacity=0;});
|
|
}
|
|
|
|
const modeSelector = $("aero-mode");
|
|
// Affiche uniquement l'input correspondant au mode courant
|
|
function applyModeVisibility(){
|
|
const fixVolWrap = $("aero-fixvolwrap");
|
|
const fixMassWrap = $("aero-fixmasswrap");
|
|
if(fixVolWrap) fixVolWrap.style.display = mode==="mass"?"":"none";
|
|
if(fixMassWrap) fixMassWrap.style.display= mode==="vol"?"":"none";
|
|
}
|
|
// ---- Partage par URL (paramètres GET, tous optionnels) ----
|
|
const paramInputs = Array.from(root.querySelectorAll("input, select")).filter(el=>el.id&&el.id.startsWith("aero-"));
|
|
const keyOf = el => el.id.slice(5);
|
|
const defaults = new Map(paramInputs.map(el=>[el, el.value]));
|
|
function applyUrlParams(){
|
|
const params = new URLSearchParams(window.location.search);
|
|
paramInputs.forEach(el=>{ const k=keyOf(el); if(params.has(k)) el.value=params.get(k); });
|
|
}
|
|
function syncUrl(){
|
|
try {
|
|
const params = new URLSearchParams();
|
|
paramInputs.forEach(el=>{ if(el.value!==defaults.get(el)) params.set(keyOf(el), el.value); });
|
|
const qs = params.toString();
|
|
window.history.replaceState(null, "", window.location.pathname + (qs?"?"+qs:"") + window.location.hash);
|
|
} catch(e){ console.error('calculateur legacy: syncUrl error:', e); }
|
|
}
|
|
|
|
if(modeSelector) {
|
|
modeSelector.addEventListener("change",e=>{
|
|
mode=e.target.value;
|
|
applyModeVisibility();
|
|
drawChart();
|
|
syncUrl();
|
|
});
|
|
}
|
|
|
|
root.querySelectorAll("input,select").forEach(el=>el.addEventListener("input",()=>{
|
|
try {
|
|
readConsts(); updateProp(); updateBalloon(); updateVolume(); drawChart(); syncUrl();
|
|
} catch(e) {
|
|
console.error('calculateur legacy: error during input event:', e);
|
|
}
|
|
}));
|
|
window.addEventListener("resize",()=>{ if(geom) drawChart(); });
|
|
|
|
try {
|
|
applyUrlParams();
|
|
if(modeSelector) mode = modeSelector.value;
|
|
applyModeVisibility();
|
|
readConsts(); updateProp(); updateBalloon(); updateVolume(); drawChart();
|
|
} catch(e) {
|
|
console.error('calculateur legacy: error during initialization:', e);
|
|
}
|
|
})();
|