Configurație de referință
Linie completă pentru structuri metalice
De la profilul brut de 12 m la structura vopsită, într-un singur flux. Configurație de referință, dimensionată de la 100 de tone pe lună: sablare, găurire CNC, debitare cu șanfren, sudură, sablarea subansamblelor și vopsire.
Simulare 3D interactivă
Aceeași linie, în 3D: rotești modelul, urmărești profilul de la intrare până la ieșire și vezi care utilaj lucrează în fiecare etapă. Poți porni „Turul cinematic”, poți opri straturile pe care nu le vrei, poți comuta între profil și tablă și poți vedea duratele reale ale etapelor.
Ce este această linie
Un constructor de structuri metalice pierde timp și bani în aceleași locuri: profilele se sablează la terți, se trasează și se găuresc manual, iar subansamblele ajung la vopsit după ce se oxidează din nou. Linia de mai jos închide fluxul în interiorul halei, în formă de U, cu un singur PLC de linie și o singură recepție.
Este o configurație de referință, calculată pe un caiet de sarcini real: profile laminate la cald de la IPE 80 până la HEB și HEM 1000, profile de 12 m, de până la 4,19 t fiecare, curățare SA 2,5 conform ISO 8501-1. Nu o livrăm identică la toți: o adaptăm pe profilele, tonajul și hala fiecărui client.
Cele șapte module
Fiecare modul are rolul lui în flux, iar împreună elimină manipularea și transportul dus-întors la terți.

1Tunel de sablare cu transport pe role
Curăță profilul brut pe toate fețele, într-o singură trecere, înainte de debitare și sudură, când geometria piesei e încă simplă.
- 6 turbine × 11 kW, abraziv alice și grit de oțel în circuit închis
- Fereastră utilă 1.700 × 1.200 mm; profile și tablă, piesă până la 1.500 × 1.000 × 12.000 mm
- Viteză de curățare 0,5 – 1,5 m/min, reglabilă
- Separare abraziv peste 99,5%; filtrare cu emisii de maximum 5 mg/m³
- Mese de role de 12 m la intrare și la ieșire


2Găurire CNC și debitare cu șanfren
Un ax vertical pentru inimă și două axe orizontale pentru tălpi găuresc simultan. Ferăstrăul-bandă în construcție tandem taie înclinat, pentru șanfren. Linia este Akyapak. Gama completă o găsești în secțiunea noastră de linii de debitare și procesare profile.
- 3 axe cu schimbător automat de scule, găuri Ø 8 – 50 mm
- Profile de la 200 × 80 mm până la 1.200 × 500 mm
- Filetare M10 – M24, marcare, măsurare automată a materialului
- Software CAM cu import de modele 3D, fără trasaj
- Conveioare de 12 m și transferuri transversale

3–5Trei zone de asamblare și sudură
Fiecare zonă are doi operatori. Sudura este operația care dă ritmul întregii linii: 90 de minute din cele 213 ale ciclului.
- Patru instalații MIG/MAG cu puls de 400 A: trei active și una de rezervă
- Zone de 18 × 7 m, cu pachete de legătură de 10 sau 15 m
- Rotatoare cu lanț pentru întoarcerea subansamblelor, fără pod rulant
- Filtrare a fumului de sudură: 2 unități × 16.000 m³/h, cu recirculare în hală
- Dotare standard: detecția scânteilor și convertizor de frecvență



6–7Cameră de sablare și incintă de vopsire
Subansamblul sudat intră în camera de sablare pentru cordoane și tăieturi, apoi trece direct în incinta de vopsire. Ușile sunt pe ambele capete, deci piesa nu se întoarce.
- Două spații de 13 × 4 × 4 m, cu acces pe ambele capete
- Cameră de sablare: SA 2,5, recuperare mecanică pe toată podeaua, emisii sub 1 mg/m³
- Vopsire cu flux longitudinal de aer, circa 18.000 m³/h, față de peste 50.000 m³/h la flux vertical
- Uscare forțată, iluminat antiex, conformitate CE și ATEX
Camera de sablare și incinta de vopsire sunt, în această configurație, specificație preliminară: parametrii se confirmă cu producătorul înainte de ofertă.
Pas cu pas: ce se întâmplă cu un profil de 12 m
Cele 13 etape de mai jos sunt cele din simularea 3D. Duratele sunt calculate pe un avans de 1 m/min, atât în tunel, cât și pe linia de găurire.
Operația critică e sudura: 90 de minute din 213, adică 42% din ciclu. De aceea sunt trei zone de asamblare, câte doi operatori fiecare. Restul liniei are rezervă: tunelul de sablare e ocupat între 0,4 și 4,5 ore pe zi, chiar și când procesează numai profile ușoare. Dacă volumul crește, se adaugă zone de sudură, nu un al doilea tunel.
Cum se așază în hală
Fluxul se închide în formă de U. Linia de sablare și cea de găurire stau pe peretele lung și lasă liber un culoar central de 11,5 m pe toată lungimea halei, pentru poduri rulante și stivuitor.

- Tunelul de sablare cere 31,5 m liniari continui; deschiderile de 21 m, fără stâlpi intermediari, îndeplinesc condiția
- Linia de găurire cu mesele ei cere 41,5 m liniari
- În fața ușilor camerelor trebuie să rămână liber un culoar de 13 m, pentru piese de 12 m
- Înălțimea liberă sub grindă trebuie să permită gabaritele de 4 m ale camerelor și ale unităților de filtrare
Cerințele unei hale pentru astfel de instalații sunt descrise în articolul ce cere hala pentru o instalație de sablare.
Ce trebuie să asigure hala
Din calcul rezultă și lista de lucruri care cad în sarcina beneficiarului. Câteva dintre ele durează mai mult decât construcția utilajelor:
- Alimentare electrică: putere instalată estimată de circa 450 kW; recomandăm un post de transformare de 630 kVA. Procedura de racord este termenul cel mai lung din proiect, așa că se pornește din timp.
- Aer comprimat: circa 13 m³/min la 7 bar pentru camera de sablare, cu uscător, rezervor tampon și rețea de distribuție.
- Pod rulant: pentru profilele de 12 m și pentru subansamble, pe toată lungimea halei.
- Lucrări de construcții: nișă de circa 130 mm în pardoseala camerei de sablare, fundații locale și trasee de cabluri; se prevăd în faza de fundații.
- Montaj: un motostivuitor sau o macara și una-două persoane pe durata montajului.
Ce intră în livrarea Vibroblast
Toate cele șapte module, manipularea dintre ele (spațiu de depozitare, transferuri transversale, mese de role), automatizarea de linie sub un singur PLC cu pupitru central, transportul, montajul, punerea în funcțiune, instruirea și documentația. Recepția se face pe întreaga linie, cu test de producție pe profilele beneficiarului și verificarea gradului de curățare SA 2,5. Termenul de livrare orientativ este de 5 – 6 luni de la comanda fermă.
Spune-ne ce profile procesezi
Trimite-ne tipul și dimensiunea maximă a profilelor, câte tone pe lună și planul halei. Pe baza lor îți propunem o configurație și îți spunem ce cere ca energie, aer comprimat și amplasare. Nu trebuie să iei tot deodată: majoritatea încep cu etapa care doare cel mai tare, de obicei sablarea.
Citește și
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Linie completă pentru structuri metalice - flux 3D</title>
<style>
:root{--maro:#6B1F3A;--crem:#F2EDE7;--text:#EAE4DC;--mut:#9A8F94;--accent:#E0A63C}
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#151016;font-family:'Segoe UI',system-ui,sans-serif;color:var(--text)}
#scena{position:fixed;inset:0}
.panou{position:fixed;background:rgba(24,16,20,.88);border:1px solid rgba(224,166,60,.25);
border-radius:10px;backdrop-filter:blur(8px);padding:14px 18px}
#titlu{top:16px;left:16px;max-width:330px}
#titlu h1{font-size:15px;letter-spacing:.14em;color:var(--accent);font-weight:600;text-transform:uppercase}
#titlu .sub{font-size:11.5px;color:var(--mut);margin-top:3px}
#etapa{top:16px;right:16px;min-width:260px;text-align:right}
#etapa .nr{font-size:11px;color:var(--mut);letter-spacing:.2em;text-transform:uppercase}
#etapa .nume{font-size:19px;font-weight:600;margin-top:2px}
#etapa .desc{font-size:11.5px;color:var(--mut);margin-top:4px;max-width:260px;margin-left:auto}
#bara{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);display:flex;gap:14px;align-items:center;z-index:20;
padding:12px 20px;white-space:nowrap}
button{background:var(--maro);border:none;color:var(--crem);font-size:13px;padding:8px 16px;border-radius:6px;
cursor:pointer;font-family:inherit;font-weight:600;letter-spacing:.03em}
button:hover{background:#83304C}
button.secundar{background:transparent;border:1px solid rgba(234,228,220,.28);font-weight:400}
button.secundar.activ{border-color:var(--accent);color:var(--accent)}
input[type=range]{-webkit-appearance:none;background:transparent;cursor:pointer}
input[type=range]::-webkit-slider-runnable-track{height:3px;background:rgba(234,228,220,.25);border-radius:2px}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;
background:var(--accent);margin-top:-5.5px}
#scrub{width:280px}
#viteza{width:90px}
.eticheta{font-size:10.5px;color:var(--mut);letter-spacing:.08em;text-transform:uppercase}
#progres-etape{position:fixed;left:50%;bottom:74px;transform:translateX(-50%);display:flex;gap:4px}
.pas{width:22px;height:4px;border-radius:2px;background:rgba(234,228,220,.15);transition:background .3s}
.pas.facut{background:var(--accent)}
.pas.acum{background:var(--crem)}
#legenda{bottom:96px;left:16px;font-size:11px;line-height:1.9;color:var(--mut);z-index:5}
.cub{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:7px;vertical-align:-1px}
#straturi{top:150px;left:16px;font-size:12px;max-width:200px}
#straturi .titluS{font-size:10.5px;color:var(--mut);letter-spacing:.12em;text-transform:uppercase;margin-bottom:7px}
#straturi label{display:block;margin:4px 0;cursor:pointer;user-select:none}
#straturi input{accent-color:var(--accent);margin-right:8px;vertical-align:-1px}
/* ===== MOBIL ===== */
@media (max-width:900px){
#legenda{display:none}
#titlu{max-width:none;left:8px;right:8px;top:8px;padding:9px 12px}
#titlu h1{font-size:12.5px}
#titlu .sub{display:none}
#etapa{left:8px;right:8px;top:auto;bottom:104px;min-width:0;text-align:left;padding:9px 12px}
#etapa .nume{font-size:15px}
#etapa .desc{font-size:11px;max-width:none;margin-left:0}
#straturi{display:none;left:8px;right:8px;top:64px;max-width:none;max-height:44vh;overflow:auto;z-index:30}
#straturi.deschis{display:block}
#progres-etape{bottom:186px;gap:3px}
.pas{width:14px;height:3px}
#bara{left:8px;right:8px;bottom:8px;transform:none;flex-wrap:wrap;gap:7px;padding:9px 10px;
justify-content:center;white-space:normal}
#bara button{font-size:12px;padding:8px 11px;flex:0 0 auto}
#scrub{width:100%;order:-1;margin-bottom:2px}
#viteza{width:70px}
#bara .eticheta{display:none}
#turBara{bottom:auto;top:76px;padding:0 14px}
#turProiect{font-size:10.5px!important;letter-spacing:.16em!important}
#marca{right:10px;bottom:auto;top:190px;opacity:.9}
#marca div:first-child{font-size:12px!important}
#marca div:last-child{font-size:9.5px!important}
#turTxt{font-size:15px!important}
#toast{left:8px;right:8px;top:64px;font-size:12px}
#btn-straturi{display:inline-block}
}
#btn-straturi{display:none}
</style>
<style>#btn-muta,#btn-200,#btn-salveaza,#btn-incarca,#btn-resetpoz,#fis-layout,#btn-foto{display:none!important}</style><style>
@media (min-width:901px){
#legenda{display:none}
#bara{flex-wrap:wrap;white-space:normal;max-width:calc(100% - 24px);justify-content:center;gap:8px 10px;padding:9px 12px}
#bara .eticheta{display:none}
#bara button{font-size:12px;padding:7px 11px}
#straturi{top:150px;font-size:11px;max-width:190px;line-height:1.25}
#titlu{max-width:290px;padding:10px 14px}
#titlu .sub{font-size:10.5px}
#etapa{min-width:230px}
}
@media (max-width:900px){
#titlu{display:none}
#etapa{top:8px!important;bottom:auto!important;left:8px;right:8px;padding:7px 11px}
#etapa .nume{font-size:14px}
#etapa .desc{font-size:10.5px;line-height:1.3}
#bara{padding:6px 8px;gap:5px 6px}
#bara button{font-size:11px;padding:6px 8px}
#marca{display:none}
}
</style></head>
<body>
<div id="scena"></div>
<div class="panou" id="titlu">
<h1>Vibroblast · Linie completă pentru structuri metalice</h1>
<div class="sub">Linie completă de producție — hală de 42,8 × 74,8 m, flux în U. Rotire cu mouse-ul, zoom cu scroll, deplasare cu clic dreapta.</div>
</div>
<div class="panou" id="etapa">
<div class="nr">ETAPA <span id="et-nr">1</span> / 13</div>
<div class="nume" id="et-nume">Spațiu de depozitare material brut</div>
<div class="desc" id="et-desc"></div>
<div id="et-timp" style="display:none;margin-top:6px;font-size:11.5px;color:#E0A63C;font-weight:600"></div>
</div>
<div id="progres-etape"></div>
<div class="panou" id="bara">
<button id="btn-play">Pornire</button>
<input type="range" id="scrub" min="0" max="1000" value="0">
<span class="eticheta">Viteză</span>
<input type="range" id="viteza" min="20" max="300" value="100">
<button class="secundar" id="btn-urmarire">Urmărește profilul</button>
<button class="secundar" id="btn-camera">Vedere generală</button>
<button class="secundar" id="btn-foto">Captură</button>
<span class="eticheta">Lumină</span>
<input type="range" id="lumina" min="40" max="180" value="100" style="width:80px">
<button class="secundar" id="btn-straturi">Straturi</button>
<button id="btn-tur" style="background:#E0A63C;color:#3E1122">Tur cinematic</button>
<button class="secundar" id="btn-paralel">Sablare: 1 profil</button>
<button class="secundar" id="btn-tabla">Tablă</button>
<button class="secundar" id="btn-timp">Timp real</button>
<button class="secundar" id="btn-multi">Flux continuu</button>
<button class="secundar" id="btn-200">Test 200 t/lună</button>
<button class="secundar" id="btn-muta">Muta echipamente</button>
<button class="secundar" id="btn-resetpoz" style="display:none">Reset pozitii</button>
<button class="secundar" id="btn-salveaza" style="display:none">Salveaza layout</button>
<button class="secundar" id="btn-incarca" style="display:none">Incarca layout</button>
<input type="file" id="fis-layout" accept=".json" style="display:none">
</div>
<div class="panou" id="legenda">
<span class="cub" style="background:#3A3F46"></span>Sablare (tunel + role)<br>
<span class="cub" style="background:#7BA88A"></span>Găurire și debitare<br>
<span class="cub" style="background:#D8C271"></span>Zone de sudură<br>
<span class="cub" style="background:#A98BC0"></span>Camere sablare / vopsire<br>
<span class="cub" style="background:#C9A23F"></span>Poduri rulante
</div>
<div class="panou" id="straturi">
<div class="titluS">Straturi vizibile</div>
<label><input type="checkbox" data-strat="hala" checked>Hala (podea, stâlpi, porți)</label>
<label><input type="checkbox" data-strat="pereti" checked>Pereții halei</label>
<label><input type="checkbox" data-strat="sablare" checked>Linia de sablare</label>
<label><input type="checkbox" data-strat="gaurire" checked>Găurire + debitare</label>
<label><input type="checkbox" data-strat="sudura" checked>Zona de sudură</label>
<label><input type="checkbox" data-strat="camere" checked>Camere + depozit</label>
<label><input type="checkbox" data-strat="poduri" checked>Poduri rulante</label>
<label><input type="checkbox" data-strat="operatori" checked>Operatori</label>
<label><input type="checkbox" data-strat="etichete" checked>Etichete</label>
<div class="titluS" style="margin-top:10px">Trasee de utilități</div>
<label><input type="checkbox" data-strat="aer">Aer comprimat</label>
<label><input type="checkbox" data-strat="electric">Alimentare electrică</label>
<label><input type="checkbox" data-strat="aspiratie">Aspirație și evacuare</label>
</div>
<div id="turBara" style="display:none;position:fixed;left:0;right:0;bottom:132px;text-align:center;pointer-events:none">
<div id="turProiect" style="font-family:'Segoe UI',sans-serif;font-size:13px;font-weight:600;
color:#E0A63C;letter-spacing:.22em;text-transform:uppercase;text-shadow:0 2px 18px rgba(0,0,0,.95);margin-bottom:6px">
Linie completă · 100 t / lună</div>
<span id="turTxt" style="font-family:'Segoe UI',sans-serif;font-size:26px;font-weight:600;color:#F2EDE7;
text-shadow:0 2px 24px rgba(0,0,0,.9);letter-spacing:.02em;transition:opacity .3s"></span></div>
<div id="marca" style="position:fixed;right:18px;bottom:96px;text-align:right;pointer-events:none;z-index:5">
<div style="font-family:'Segoe UI',sans-serif;font-size:15px;font-weight:700;color:#E0A63C;letter-spacing:.16em">VIBROBLAST</div>
<div style="font-family:'Segoe UI',sans-serif;font-size:11px;color:#C9BFC4;letter-spacing:.05em;margin-top:2px">
De la profilul brut la structura vopsită</div>
</div>
<div class="panou" id="contor" style="display:none;top:150px;right:16px;min-width:255px">
<div style="font-size:10.5px;color:#9A8F94;letter-spacing:.14em;text-transform:uppercase;margin-bottom:8px">Încărcarea modulelor</div>
<div id="contorLista"></div>
<div style="border-top:1px solid rgba(234,228,220,.16);margin-top:9px;padding-top:9px;font-size:12px">
<span style="color:#9A8F94">Scenariu</span> <b id="contorScenariu" style="float:right">100 t/lună</b><br>
<span style="color:#9A8F94">Ciclu / subansamblu</span> <b style="float:right">3 h 33 min</b>
</div>
</div>
<div class="panou" id="toast" style="display:none;top:112px;right:16px;font-size:13px"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
var HALA_L=74.8, HALA_W=42.8, H_PERETE=10, AXA=3.84;
var scena=new THREE.Scene();
scena.background=new THREE.Color(0x151016);
scena.fog=new THREE.Fog(0x151016,130,280);
var camera=new THREE.PerspectiveCamera(48,innerWidth/innerHeight,.1,600);
var rend=new THREE.WebGLRenderer({antialias:true,alpha:false});
rend.setSize(innerWidth,innerHeight);
rend.setPixelRatio(Math.min(devicePixelRatio,2));
rend.shadowMap.enabled=true; rend.shadowMap.type=THREE.PCFSoftShadowMap;
rend.toneMapping=THREE.ACESFilmicToneMapping; rend.toneMappingExposure=0.95;
rend.outputEncoding=THREE.sRGBEncoding;
document.getElementById('scena').appendChild(rend.domElement);
scena.add(new THREE.AmbientLight(0xffffff,.42));
var soare=new THREE.DirectionalLight(0xfff6ea,1.6);
soare.position.set(50,70,40); soare.castShadow=true;
soare.shadow.camera.left=-75; soare.shadow.camera.right=75;
soare.shadow.camera.top=75; soare.shadow.camera.bottom=-75;
soare.shadow.mapSize.set(2048,2048); scena.add(soare);
var umplere=new THREE.DirectionalLight(0x9FB4FF,.3);
umplere.position.set(-40,50,-40); scena.add(umplere);
/* ORIENTARE: X = L (portile la stanga), Z = est spre privitor (linia jos) */
function poz(L,W,y){return new THREE.Vector3(L-HALA_L/2, y||0, HALA_W/2-W);}
var grup=new THREE.Group(); scena.add(grup);
var ANIM={oameni:[]};
var STRAT_ON={hala:true,pereti:true,sablare:true,gaurire:true,sudura:true,camere:true,poduri:true,operatori:true,etichete:true};
function mat(c,opt){var o=opt||{};
var m=new THREE.MeshStandardMaterial({color:c,roughness:o.r!==undefined?o.r:.6,
metalness:o.m||0,transparent:o.o!==undefined,opacity:o.o!==undefined?o.o:1});
/* utilajele primesc o emisie proprie discreta ca sa nu se piarda pe fundal inchis */
if(!(o.r>=.9)){ m.emissive=new THREE.Color(c); m.emissiveIntensity=0.22; }
return m;}
function cutie(L,W,y,dl,dw,h,material,umbre){
var ms=new THREE.Mesh(new THREE.BoxGeometry(dl,h,dw),material);
ms.position.copy(poz(L,W,y)); if(umbre!==false){ms.castShadow=true;ms.receiveShadow=true;}
grup.add(ms); return ms;
}
/* ---------- hala ---------- */
var podea=cutie(HALA_L/2,HALA_W/2,-0.15,HALA_L+0.8,HALA_W+0.8,0.3,mat(0x1C1A24,{r:1.0}));
podea.castShadow=false;
var gmat=new THREE.LineBasicMaterial({color:0x3A3550});
for(var l=0;l<=HALA_L+.1;l+=10.6){
var g=new THREE.BufferGeometry().setFromPoints([poz(l,0,.01),poz(l,HALA_W,.01)]);
grup.add(new THREE.Line(g,gmat));
}
var matPerete=new THREE.MeshStandardMaterial({color:0x6B6480,roughness:.9,
side:THREE.BackSide,transparent:true,opacity:0.16,depthWrite:false});
function zid(L,W,dl,dw,h,y){
var m=cutie(L,W,(y||0)+h/2,dl,dw,h,matPerete,false);
m.userData.strat='pereti';
/* muchie subtire, ca hala sa ramana citibila si cand peretii sunt stinsi */
var ed=new THREE.LineSegments(new THREE.EdgesGeometry(m.geometry),
new THREE.LineBasicMaterial({color:0x8A83A8,transparent:true,opacity:.5}));
ed.position.copy(m.position); ed.userData.strat='pereti'; grup.add(ed);
return m;}
zid(HALA_L/2,0,HALA_L,.3,H_PERETE); // est
zid(HALA_L/2,HALA_W,HALA_L,.3,H_PERETE); // vest
zid(HALA_L,HALA_W/2,.3,HALA_W,H_PERETE); // nord
zid(0,5.475,.3,10.95,H_PERETE); // sud, intre porti
zid(0,21.4,.3,10.9,H_PERETE);
zid(0,37.325,.3,10.95,H_PERETE);
zid(0,13.45,.3,5,H_PERETE-5.5,5.5);
zid(0,29.35,.3,5,H_PERETE-5.5,5.5);
function poarta(W,c){
var m=cutie(0,W,2.75,.35,5,5.5,mat(c,{o:.5}));
m.material.emissive=new THREE.Color(c); m.material.emissiveIntensity=.25;
}
poarta(13.45,0x2E9E5B); poarta(29.35,0x3B6E8F);
for(var l2=.92;l2<HALA_L;l2+=10.6) cutie(l2,21.56,H_PERETE/2,.8,.6,H_PERETE,mat(0x59505F,{r:.7}));
/* ================= ATMOSFERA ================= */
scena.background=new THREE.Color(0x16141E);
scena.fog=null;
/* lumina de umplere calda dinspre podea (bounce) */
var bounce=new THREE.HemisphereLight(0xCFE0FF,0x3B3746,.4); scena.add(bounce);
/* corpuri de iluminat industriale suspendate */
var LAMPI=[];
for(var li=8;li<HALA_L;li+=13){
for(var lw=9;lw<HALA_W;lw+=12){
var cupa=new THREE.Mesh(new THREE.ConeGeometry(.62,.5,14,1,true),
new THREE.MeshStandardMaterial({color:0x4A4350,side:THREE.DoubleSide,roughness:.6}));
cupa.position.copy(poz(li,lw,8.4)); grup.add(cupa);
var bec=new THREE.Mesh(new THREE.SphereGeometry(.2,10,10),
new THREE.MeshBasicMaterial({color:0xFFF3DC}));
bec.position.copy(poz(li,lw,8.15)); grup.add(bec); LAMPI.push(bec);
var tija=new THREE.Mesh(new THREE.CylinderGeometry(.03,.03,1.4,6),mat(0x3A3440));
tija.position.copy(poz(li,lw,9.3)); grup.add(tija);
var pl=new THREE.PointLight(0xFFE9C4,.42,26,2);
pl.position.copy(poz(li,lw,8.0)); scena.add(pl);
}
}
/* raze de lumina prin luminatoarele de acoperis */
var RAZE=[];
for(var ri=12;ri<HALA_L-6;ri+=16){
var raza=new THREE.Mesh(new THREE.CylinderGeometry(2.6,5.2,H_PERETE+2,16,1,true),
new THREE.MeshBasicMaterial({color:0xFFE7BC,transparent:true,opacity:.055,
side:THREE.DoubleSide,depthWrite:false,blending:THREE.AdditiveBlending}));
raza.visible=false;
raza.position.copy(poz(ri,HALA_W*0.42,(H_PERETE+2)/2)); raza.rotation.z=.10;
scena.add(raza); RAZE.push(raza);
}
/* praf in suspensie, prins de lumina */
(function(){
var n=900, g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
for(var i=0;i<n;i++){
var q=poz(Math.random()*HALA_L,Math.random()*HALA_W,.4+Math.random()*7.5);
p[i*3]=q.x; p[i*3+1]=q.y; p[i*3+2]=q.z;
}
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var praf=new THREE.Points(g,new THREE.PointsMaterial({color:0xFFE2B0,size:.075,
transparent:true,opacity:.28,depthWrite:false,blending:THREE.AdditiveBlending}));
scena.add(praf); ANIM.praf=praf; ANIM.prafBase=p.slice();
})();
/* ---------- etichete ---------- */
function etich(txt,L,W,y,scara){
var c=document.createElement('canvas');c.width=512;c.height=110;
var g=c.getContext('2d');
g.font='600 46px Segoe UI, sans-serif';g.textAlign='center';
var w=g.measureText(txt).width+44;
g.fillStyle='rgba(16,12,18,.88)';
g.beginPath();g.rect((512-w)/2,12,w,80);g.fill();
g.fillStyle='#EAE4DC';g.fillText(txt,256,68);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:new THREE.CanvasTexture(c),depthTest:false}));
var s=(scara||11)*((innerWidth<900)?0.78:0.82); sp.scale.set(s,s*110/512,1);
sp.position.copy(poz(L,W,y)); grup.add(sp);
}
/* ---------- role ---------- */
var matRola=mat(0xB0342E,{m:.35,r:.45}), matCadru=mat(0x2E5C8A,{r:.6}), matSina=mat(0xD8C231,{r:.55});
function role(L1,L2,W){
cutie((L1+L2)/2,W,.45,L2-L1,2,.55,matCadru);
cutie((L1+L2)/2,W-0.97,.76,L2-L1,.12,.1,matSina);
cutie((L1+L2)/2,W+0.97,.76,L2-L1,.12,.1,matSina);
for(var l=L1+.4;l<L2;l+=.8){
var r=new THREE.Mesh(new THREE.CylinderGeometry(.11,.11,1.8,10),matRola);
r.rotation.x=Math.PI/2; r.position.copy(poz(l,W,.86)); grup.add(r);
}
for(var l3=L1+2;l3<L2;l3+=4) cutie(l3,W+1.25,.5,.5,.4,.4,matCadru); // motoare
}
role(0.5,12.5,AXA); role(20,32,AXA); role(32,44,AXA); role(61.5,73.5,AXA);
/* ---------- TUNEL DE SABLARE (reconstruit dupa referinta) ---------- */
(function(){
var gri=mat(0x9AA1AA,{r:.6}), inchis=mat(0x3A3F46,{r:.55}), galben=mat(0xE0C231,{r:.55});
/* soclu de beton */
cutie(16.2,AXA,.05,8.6,5.4,.1,mat(0xB5B0A8,{r:.95}),false);
/* vestibule de etansare */
cutie(13.2,AXA,1.65,1.5,3.2,2.9,gri);
cutie(19.1,AXA,1.65,1.5,3.2,2.9,gri);
/* camera de sablare - corp inchis la culoare, ca in referinta */
cutie(16.15,AXA,2.4,4.2,3.7,4.4,inchis);
cutie(16.15,AXA,4.62,4.4,3.9,.14,gri,false);
/* dunga rosie la nivelul rolelor */
cutie(16.15,2.0,0.9,4.2,.05,.12,mat(0xB0342E));
/* 6 turbine cu motoare si discuri rotative (aceleasi pozitii) */
var matTurb=mat(0x565E68,{r:.5}), matMotor=mat(0x3E4650,{m:.3,r:.4});
var pozT=[[15.1,1.35,4.0,.5],[16.25,1.35,3.2,-.4],[17.4,1.35,4.0,.5],
[15.1,6.33,4.0,-.5],[16.25,6.33,3.2,.4],[17.4,6.33,4.0,-.5]];
ANIM.turbine=[];
for(var i=0;i<6;i++){
var p=pozT[i];
var t=new THREE.Mesh(new THREE.BoxGeometry(1.0,1.0,1.0),matTurb);
t.position.copy(poz(p[0],p[1],p[2])); t.rotation.x=p[3]; t.castShadow=true; grup.add(t);
var dirW=(p[1]<AXA)?-1:1;
var mo=new THREE.Mesh(new THREE.CylinderGeometry(.27,.27,.9,12),matMotor);
mo.position.copy(poz(p[0],p[1]+dirW*0.95,p[2])); mo.rotation.x=Math.PI/2; mo.castShadow=true; grup.add(mo);
var disc=new THREE.Mesh(new THREE.CylinderGeometry(.34,.34,.09,16),galben);
disc.rotation.x=Math.PI/2; disc.position.copy(poz(p[0],p[1]+dirW*1.45,p[2])); grup.add(disc);
ANIM.turbine.push(disc);
}
/* buncar si separator DEASUPRA camerei, pe platforma cu balustrada galbena */
var buncar=new THREE.Mesh(new THREE.CylinderGeometry(1.5,.5,1.3,4),gri);
buncar.rotation.y=Math.PI/4; buncar.position.copy(poz(16.15,AXA,5.5)); grup.add(buncar);
cutie(16.15,AXA,6.7,2.8,1.9,1.2,gri);
cutie(16.15,AXA,6.12,4.6,3.4,.14,mat(0x556070),false);
[2.2,5.5].forEach(function(w){
for(var b=0;b<5;b++) cutie(14.3+b*1.1,w,6.7,.06,.06,1.05,galben);
cutie(16.35,w,7.25,4.4,.06,.08,galben);
});
/* turn elevator cu ferestre de vizitare rosii */
cutie(16.15,6.9,3.9,1.1,.95,7.8,gri);
for(var e=1.2;e<7;e+=1.1) cutie(16.15,6.42,e,.6,.05,.5,mat(0xA8313A));
cutie(16.15,6.9,8.0,1.5,1.3,.9,gri);
/* ciclon + racord spre desprafuitor */
var cic=new THREE.Mesh(new THREE.CylinderGeometry(.6,.22,1.9,12),gri);
cic.position.copy(poz(13.9,7.7,5.2)); grup.add(cic);
/* dulap electric mare, pe pardoseala */
cutie(11.2,8.9,1.15,1.7,1.1,2.3,mat(0x565E68));
etich('TUNEL SABLARE',16.15,AXA,9.3,11);
etich('6 x TURBINE 11 kW',16.25,8.6,8.9,9);
})();
/* ---------- LINIA DE GAURIRE + FIERASTRAU ---------- */
(function(){
var verde=mat(0x7BA88A,{r:.55}), inchis=mat(0x4F7861,{r:.55});
// portal gaurire: 2 coloane + traversa + 3 unitati de ax
cutie(48.5,1.5,1.6,2.6,1.0,3.2,verde);
cutie(48.5,6.2,1.6,2.6,1.0,3.2,verde);
cutie(48.5,AXA,3.5,2.6,5.6,.9,verde); // traversa
cutie(48.5,AXA,2.6,1.0,1.0,1.2,inchis); // unitate verticala
var axV=new THREE.Mesh(new THREE.CylinderGeometry(.12,.12,1.1,10),mat(0xD8DEE4,{m:.6}));
axV.position.copy(poz(48.5,AXA,1.75)); grup.add(axV);
var axH1=new THREE.Mesh(new THREE.CylinderGeometry(.12,.12,1.0,10),mat(0xD8DEE4,{m:.6}));
axH1.rotation.x=Math.PI/2; axH1.position.copy(poz(48.5,2.6,1.35)); grup.add(axH1);
var axH2=axH1.clone(); axH2.position.copy(poz(48.5,5.1,1.35)); grup.add(axH2);
ANIM.axV=axV; ANIM.axH1=axH1; ANIM.axH2=axH2;
ANIM.axVy=axV.position.y; ANIM.axH1z=axH1.position.z; ANIM.axH2z=axH2.position.z;
cutie(48.5,1.7,1.5,1.0,.8,1.1,inchis); cutie(48.5,6.0,1.5,1.0,.8,1.1,inchis); // unitati orizontale
// magazii de scule (discuri)
for(var i=0;i<3;i++){
var d=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,.15,14),mat(0x395143));
d.position.copy(poz(47.2,1.6+i*2.5,3.1)); grup.add(d);
}
cutie(46,7.6,1.0,1.6,1.2,2.0,mat(0x5E6670)); // dulap comanda
cutie(50.6,6.9,.5,3.2,.8,.9,inchis); // transportor span
// fierastrau cu inclinare
cutie(57.5,AXA,1.5,2.4,4.6,3.0,verde);
var lama=new THREE.Mesh(new THREE.BoxGeometry(.18,2.4,2.6),inchis);
lama.position.copy(poz(57.5,AXA,2.9)); lama.rotation.y=.5; grup.add(lama);
cutie(59.2,6.4,.9,1.2,.9,1.8,mat(0x5E6670));
etich('GĂURIRE + DEBITARE',52,AXA,7.2,12);
})();
/* brat de alimentare (carucior cu clema) */
var carucior=new THREE.Group();
carucior.add(new THREE.Mesh(new THREE.BoxGeometry(1.2,.7,.8),mat(0x4F7861)));
var clema=new THREE.Mesh(new THREE.BoxGeometry(.25,.5,1.2),mat(0x395143));
clema.position.set(.4,.5,-.7); carucior.add(clema);
carucior.position.copy(poz(33,6.5,1.0)); scena.add(carucior);
cutie(46.7,6.5,.35,29,.35,.25,mat(0x39424E)); // sina carucior
/* ---------- SPATIU DE DEPOZITARE MATERIAL BRUT ---------- */
(function(){
for(var i=0;i<4;i++){
var a=new THREE.Mesh(new THREE.BoxGeometry(.25,1.6,5),mat(0x8E99A6));
a.position.copy(poz(2+i*3,12.8,.8)); grup.add(a);
}
var matBara=mat(0x8A5A3C,{r:.7,m:.3});
for(var j=0;j<3;j++){
var b=new THREE.Mesh(new THREE.BoxGeometry(12,.4,.35),matBara);
b.position.copy(poz(6.5,11.6+j*1.1,1.15)); b.castShadow=true; grup.add(b);
}
etich('SPAȚIU DEPOZITARE',6.5,12.8,3.4,10);
})();
/* ---------- ZONE SUDURA ---------- */
(function(){
var galben=mat(0xD8C271,{o:.85});
var zone=[[65,72,'SUDURĂ 1'],[57,64,'SUDURĂ 2'],[49,56,'SUDURĂ 3']];
for(var z=0;z<3;z++){
var Z=zone[z];
cutie((Z[0]+Z[1])/2,31.8,.12,Z[1]-Z[0],18,.24,galben,false);
// masa de sudura + aparat cu butelie
var mL=(Z[0]+Z[1])/2;
cutie(mL,30,1.0,3.2,1.4,.18,mat(0x50565E,{m:.3}));
for(var p=0;p<4;p++) cutie(mL+(p<2?-1.4:1.4),29.4+(p%2)*1.2,.45,.16,.16,.9,mat(0x3A3F46));
cutie(mL-2.6,28.6,.55,.7,.5,1.0,mat(0x8A2F2F));
var but=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,1.1,10),mat(0x5E7889));
but.position.copy(poz(mL-2.1,28.3,.6)); grup.add(but);
etich(Z[2],mL,31.8,4.6,9);
}
// rotatoare cu lant (2 seturi = 4 unitati)
var seturi=[[56.3,58.3],[64.3,66.3]];
for(var s=0;s<2;s++){
for(var u=0;u<2;u++){
var L=seturi[s][u];
cutie(L,24.5,.5,1.4,1.2,1.0,mat(0x4F7861));
var lant=new THREE.Mesh(new THREE.TorusGeometry(.55,.09,8,20),mat(0x39424E,{m:.4}));
lant.position.copy(poz(L,24.5,1.35)); grup.add(lant);
}
}
// filtre Tower cu brat perforat
var tw=[[64.3,41.6],[49.3,41.6]];
for(var t=0;t<2;t++){
cutie(tw[t][0],tw[t][1],2.0,1.85,1.1,4.0,mat(0x6E7883,{r:.5}));
var brat=new THREE.Mesh(new THREE.CylinderGeometry(.22,.22,4.5,10),mat(0x828B96));
brat.rotation.x=Math.PI/2; brat.position.copy(poz(tw[t][0],39.2,3.6)); grup.add(brat);
}
etich('FILTRE FUM',56.8,41.6,5.4,9);
})();
/* ---------- CAMERE (sablare + vopsire) ---------- */
function camba(L1,L2,W1,W2,nume,vopsire){
var alb=mat(0xF3F2F4,{o:.38,r:.7}), rosu=mat(0xA8313A,{r:.55}), albastru=mat(0x3B6EC0,{r:.5});
var h=4, Lm=(L1+L2)/2, Wm=(W1+W2)/2;
cutie(Lm,W1,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,W2,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,Wm,h+.05,L2-L1,W2-W1,.14,mat(0xF3F2F4,{o:.55}),false);
for(var l=L1;l<=L2+.01;l+=2.17){
cutie(l,W1,h/2,.2,.24,h,rosu); cutie(l,W2,h/2,.2,.24,h,rosu);
}
[0.12,h-0.12].forEach(function(y){
cutie(Lm,W1,y,L2-L1,.26,.2,rosu,false); cutie(Lm,W2,y,L2-L1,.26,.2,rosu,false);
});
[[L1,-1],[L2,1]].forEach(function(cap){
cutie(cap[0],W1+.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=cap[1]*0.5;
cutie(cap[0],W2-.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=-cap[1]*0.5;
cutie(cap[0],Wm,h-.25,.2,W2-W1,.5,rosu,false);
});
if(vopsire){
cutie(L1+2,Wm,h+.65,2.2,1.6,1.2,mat(0x7E8794));
var tOr=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,(L2-L1)*.55,12),albastru);
tOr.rotation.z=Math.PI/2; tOr.position.copy(poz(Lm-1,Wm,h+.75)); grup.add(tOr);
var tVe=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,2.6,12),albastru);
tVe.position.copy(poz(L2-1.5,Wm,h+1.9)); grup.add(tVe);
}else{
cutie(Lm,W1-0.9,1.4,3.2,1.4,2.8,mat(0x7E8794));
var cos2=new THREE.Mesh(new THREE.CylinderGeometry(.2,.2,2.4,10),mat(0x8B93A0));
cos2.position.copy(poz(Lm,W1-0.9,3.9)); grup.add(cos2);
cutie(Lm,Wm,.06,L2-L1-1,1.1,.12,mat(0x3E4650));
var tOr2=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,3.4,12),albastru);
tOr2.rotation.x=Math.PI/2; tOr2.position.copy(poz(Lm,W1+0.8,h+.7)); grup.add(tOr2);
}
etich(nume,Lm,Wm,5.6,11);
}
camba(22.5,35.5,24.3,28.3,'CAMERA SABLARE',false);
camba(22.5,35.5,30.8,34.8,'CAMERA VOPSIRE',true);
cutie(11,34,.11,14,13,.22,mat(0x8E99A6,{o:.75}),false);
etich('DEPOZITARE',11,34,2.6,10);
// piese finite vopsite
var matFin=mat(0xB3352E,{r:.55,m:.2});
for(var pf=0;pf<2;pf++){
var fin=new THREE.Mesh(new THREE.BoxGeometry(10,.5,.4),matFin);
fin.position.copy(poz(10,31.5+pf*1.6,.5)); fin.castShadow=true; grup.add(fin);
}
/* ---------- PODURI RULANTE ---------- */
var matCran=mat(0xC9A23F,{r:.5}), matCran2=mat(0xA8862F,{r:.5});
function grindaCale(W){cutie(HALA_L/2,W,7.55,HALA_L,.4,.4,matCran2,false);}
grindaCale(.7); grindaCale(20.9); grindaCale(22.2); grindaCale(42.1);
function facePod(W1,W2){
var g=new THREE.Group(); var span=W2-W1;
[ -0.9, 0.9 ].forEach(function(dx){
var gi=new THREE.Mesh(new THREE.BoxGeometry(.55,.7,span),matCran);
gi.position.set(dx,0, 0); gi.castShadow=true; g.add(gi);
});
[-span/2+0.3,span/2-0.3].forEach(function(dz){
var tr=new THREE.Mesh(new THREE.BoxGeometry(2.6,.6,.7),matCran2);
tr.position.set(0,0,dz); g.add(tr);
});
var trolley=new THREE.Mesh(new THREE.BoxGeometry(1.5,.7,1.3),matCran2);
trolley.position.set(0,.7,0); g.add(trolley);
g.userData={W1:W1,W2:W2,trolley:trolley,L:30,Wt:(W1+W2)/2};
scena.add(g); return g;
}
var podE=facePod(0.9,20.7); podE.userData.L=8; podE.userData.Wt=8;
var podV=facePod(22.4,42.0); podV.userData.L=45; podV.userData.Wt=38;
var Y_POD=7.9;
// cabluri + traversa de ridicare
function faceCablu(){
var geo=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);
var ln=new THREE.Line(geo,new THREE.LineBasicMaterial({color:0xD8DEE4}));
ln.visible=false; scena.add(ln); return ln;
}
var cabluri=[faceCablu(),faceCablu(),faceCablu(),faceCablu()];
var traversa=new THREE.Mesh(new THREE.BoxGeometry(8,.22,.3),matCran2);
traversa.visible=false; scena.add(traversa);
/* ---------- DETALII v3 (documentate) ---------- */
function teava(a,b,r,c){
var d=new THREE.Vector3().subVectors(b,a), len=d.length();
var m=new THREE.Mesh(new THREE.CylinderGeometry(r,r,len,8),mat(c||0x8B93A0));
m.position.copy(a).addScaledVector(d,.5);
m.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.clone().normalize());
scena.add(m); return m;
}
/* tunel: perdele de cauciuc la intrare/iesire (etansare labirint) */
[[12.55],[19.95]].forEach(function(cap){
for(var w=2.45;w<=5.3;w+=.32){
var f=new THREE.Mesh(new THREE.BoxGeometry(.05,1.35,.27),mat(0x23262B,{r:.9}));
f.position.copy(poz(cap[0],w,1.75)); grup.add(f);
}
});
/* tevi de dozare abraziv: separator -> fiecare turbina */
var sep=poz(16.25,5.9,6.15);
[[15.1,1.35,4.0],[16.25,1.35,3.2],[17.4,1.35,4.0],[15.1,6.33,4.0],[16.25,6.33,3.2],[17.4,6.33,4.0]]
.forEach(function(p){ teava(sep,poz(p[0],p[1],p[2]+.55),.07); });
/* duct camera -> desprafuitor + snec longitudinal sub role */
teava(poz(16.9,5.6,4.55),poz(15.4,8.3,4.3),.26);
cutie(16.5,AXA,.12,7.4,.7,.24,mat(0x39424E));
/* sistem de curatare la iesire: perie nylon + suflanta */
var perie=new THREE.Mesh(new THREE.CylinderGeometry(.2,.2,3.0,12),mat(0x6E5A3A,{r:.9}));
perie.rotation.x=Math.PI/2; perie.position.copy(poz(19.55,AXA,2.1)); grup.add(perie);
cutie(19.55,6.3,2.7,.9,.9,.9,mat(0x556070));
teava(poz(19.55,5.85,2.7),poz(19.55,4.6,2.35),.12);
/* aparatoare de lant pe o parte a fiecarei mese cu role */
[[0.5,12.5],[20,32],[32,44],[61.5,73.5]].forEach(function(t){
cutie((t[0]+t[1])/2,AXA+1.12,.86,t[1]-t[0],.1,.34,mat(0x2E353D));
});
/* perdea de alice in camera (animata) */
var perdea=new THREE.Mesh(new THREE.BoxGeometry(.14,3.3,3.1),
new THREE.MeshBasicMaterial({color:0xD8C9A0,transparent:true,opacity:0}));
perdea.position.copy(poz(16.25,AXA,2.3)); scena.add(perdea); ANIM.perdea=perdea;
/* portal gaurire: laser de zero + bacuri de strangere + pupitru mobil */
var laser=new THREE.Mesh(new THREE.BoxGeometry(.03,.03,3.3),
new THREE.MeshBasicMaterial({color:0xFF3B30}));
laser.position.copy(poz(47.1,AXA,1.06)); scena.add(laser);
cutie(48.5,2.75,1.15,.5,.35,.7,mat(0x394450)); cutie(48.5,4.95,1.15,.5,.35,.7,mat(0x394450));
cutie(44.6,6.9,.75,.7,.6,1.4,mat(0x5E6670));
/* fierastrau: descarcare magnetica */
cutie(60.2,6.1,1.9,.35,.35,3.6,mat(0x4F7861));
cutie(59.7,5.2,3.4,1.6,.3,.3,mat(0x4F7861));
cutie(59.2,4.4,2.9,.7,.5,.35,mat(0x8A2F2F));
/* ecrane de protectie sudura in jurul fiecarei zone */
[[68.5],[60.5],[52.5]].forEach(function(z){
var mE=mat(0x7A2E2E,{o:.45,r:.85});
cutie(z[0],29.1,1.1,5.0,.08,2.2,mE,false);
cutie(z[0],34.5,1.1,5.0,.08,2.2,mE,false);
cutie(z[0]+2.6,31.8,1.1,.08,5.5,2.2,mE,false);
});
/* rastel butelii linga zona 3 */
cutie(46.6,29.5,.75,1.6,.7,1.5,mat(0x4A5560));
for(var bb=0;bb<3;bb++){
var bt=new THREE.Mesh(new THREE.CylinderGeometry(.15,.15,1.3,10),mat(0x5E7889));
bt.position.copy(poz(46.2+bb*.45,29.5,.75)); grup.add(bt);
}
/* camera sablare: unitate de recuperare RU + benzi de lumina */
cutie(36.6,25.0,1.15,1.5,1.2,2.3,mat(0x7E8794));
teava(poz(35.8,25.4,2.1),poz(34.9,26.3,1.6),.14);
[[26.3],[32.8]].forEach(function(w){
[25.5,29,32.5].forEach(function(l){
var lm=new THREE.Mesh(new THREE.BoxGeometry(2.2,.06,.2),
new THREE.MeshBasicMaterial({color:0xFFF6E0}));
lm.position.copy(poz(l,w[0],3.86)); scena.add(lm);
});
});
/* incinta vopsire: perete aspirant cu grila la capatul de iesire */
cutie(22.75,32.8,1.9,.16,3.6,3.4,mat(0x5A6470,{o:.85}),false);
for(var gz=0;gz<5;gz++) cutie(22.83,31.35+gz*.72,1.9,.05,.08,3.0,mat(0x8B93A0),false);
/* poduri: carucior cu tambur si carlig */
[podE,podV].forEach(function(p){
var tb=new THREE.Mesh(new THREE.CylinderGeometry(.22,.22,.7,10),mat(0x6E5A20));
tb.rotation.z=Math.PI/2; tb.position.set(0,1.15,0); p.userData.trolley.add(tb);
});
/* referinta izometrica: palnii conice sub desprafuitoare + scara de acces */
[[13.6,7.4],[13.6,9.4],[17.2,7.4],[17.2,9.4]].forEach(function(pp){
cutie(pp[0],pp[1],0.6,.18,.18,1.2,mat(0x6A7280));
});
var palnie=new THREE.Mesh(new THREE.CylinderGeometry(1.05,.22,1.5,4),mat(0x6A7280));
palnie.rotation.y=Math.PI/4; palnie.position.copy(poz(15.4,8.4,-0.05)); grup.add(palnie);
cutie(12.4,9.6,2.0,1.8,1.6,4.0,mat(0x7E8794,{r:.55}));
var palnie2=new THREE.Mesh(new THREE.CylinderGeometry(.8,.2,1.3,4),mat(0x6A7280));
palnie2.rotation.y=Math.PI/4; palnie2.position.copy(poz(12.4,9.6,0)); grup.add(palnie2);
cutie(17.1,7.3,2.6,.08,.5,5.2,mat(0x3E8E4E));
for(var tt=0.5;tt<5;tt+=.45) cutie(17.1,7.3,tt,.3,.42,.05,mat(0x3E8E4E));
/* ---------- OPERATORI ---------- */
function persoana(cCasca,cVesta,cPant){
var g=new THREE.Group();
var mPant=mat(cPant||0x39424E,{r:.8}), mVesta=mat(cVesta||0xD97B29,{r:.8}), mPiele=mat(0xC9A183,{r:.8});
[-0.09,0.09].forEach(function(x){
var p=new THREE.Mesh(new THREE.BoxGeometry(.15,.75,.15),mPant);
p.position.set(x,.375,0); p.castShadow=true; g.add(p);
});
var tors=new THREE.Mesh(new THREE.BoxGeometry(.42,.6,.24),mVesta);
tors.position.set(0,1.05,0); tors.castShadow=true; g.add(tors);
var cap=new THREE.Mesh(new THREE.SphereGeometry(.13,10,10),mPiele);
cap.position.set(0,1.5,0); g.add(cap);
var casca=new THREE.Mesh(new THREE.SphereGeometry(.15,10,8,0,Math.PI*2,0,Math.PI/2),mat(cCasca,{r:.5}));
casca.position.set(0,1.52,0); g.add(casca);
function brat(x){
var piv=new THREE.Group(); piv.position.set(x,1.32,0);
var b=new THREE.Mesh(new THREE.BoxGeometry(.11,.55,.11),mVesta);
b.position.set(0,-.27,0); b.castShadow=true; piv.add(b); g.add(piv); return piv;
}
var bs=brat(-.27), bd=brat(.27);
bs.rotation.z=.15; bd.rotation.z=-.15;
return {g:g,bs:bs,bd:bd,tors:tors};
}
function pune(om,L,W,rot){om.g.position.copy(poz(L,W,0));om.g.rotation.y=rot||0;scena.add(om.g);return om;}
/* supraveghetor sablare - la pupitrul de langa tunel */
cutie(11.6,6.6,.85,.5,.5,.5,mat(0x5E6670)); // pupitru
var opSab=pune(persoana(0xF2EDE7,0xD97B29),11.6,7.4,Math.PI);
/* operator gaurire+debitare - la dulapul de comanda */
var opGaur=pune(persoana(0xF2EDE7,0xD97B29),45.0,7.3,Math.PI+.4);
/* sudori: 2 pe fiecare zona */
var sudori=[];
[[68.5],[60.5],[52.5]].forEach(function(z,zi){
var s1=pune(persoana(0xB03030,0x4A5560),z[0]-1.7,30.6,0);
var s2=pune(persoana(0xB03030,0x4A5560),z[0]+1.7,33.0,Math.PI);
sudori.push([s1,s2]);
});
/* pistolete sudura cu varf luminos, doar zona 1 */
function pistolet(){
var g=new THREE.Group();
var corp=new THREE.Mesh(new THREE.BoxGeometry(.08,.3,.08),mat(0x2A2E33));
corp.position.y=-.15; g.add(corp);
var varf=new THREE.Mesh(new THREE.SphereGeometry(.05,8,8),
new THREE.MeshBasicMaterial({color:0xBFDcFF}));
varf.position.y=-.34; g.add(varf); g.userData.varf=varf;
scena.add(g); g.visible=false; return g;
}
var pist1=pistolet(), pist2=pistolet();
/* sablator in camera - cu furtun si duza */
var opCamSab=pune(persoana(0x8899AA,0x8899AA,0x707A85),27.2,27.7,Math.PI*1.5);
var jetSab=new THREE.Mesh(new THREE.ConeGeometry(.5,2.6,10,1,true),
new THREE.MeshBasicMaterial({color:0xD8C9A0,transparent:true,opacity:.28,side:THREE.DoubleSide}));
jetSab.visible=false; scena.add(jetSab);
var furtunSab=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,4.5,8),mat(0x2A2E33));
furtunSab.visible=false; scena.add(furtunSab);
/* vopsitor in incinta - combinezon alb, pistol cu jet */
var opVops=pune(persoana(0xF2EDE7,0xE8E4DE,0xD8D4CE),31.5,33.9,Math.PI*1.5);
var jetVops=new THREE.Mesh(new THREE.ConeGeometry(.42,2.0,10,1,true),
new THREE.MeshBasicMaterial({color:0xE0483C,transparent:true,opacity:.32,side:THREE.DoubleSide}));
jetVops.visible=false; scena.add(jetVops);
/* ---------- PROFILUL, cu evolutie pe etape ---------- */
var grupProfil=new THREE.Group(); scena.add(grupProfil);
var CUL={
rugina:new THREE.Color(0x8E4B30), /* otel laminat, oxidat */
sa3: new THREE.Color(0xC6CCD4), /* curatat SA 3, metal alb */
cordon:new THREE.Color(0x24242A), /* cordon de sudura brut */
grund: new THREE.Color(0xA85440) /* grund sudabil */
};
var matProfil=new THREE.MeshStandardMaterial({color:CUL.rugina.clone(),roughness:.92,metalness:.15});
matProfil.emissive=new THREE.Color(CUL.rugina); matProfil.emissiveIntensity=.16;
var matCordon=new THREE.MeshStandardMaterial({color:CUL.cordon.clone(),roughness:.75,metalness:.3});
var matGaura =new THREE.MeshStandardMaterial({color:0x15151A,roughness:1});
function grinda(len,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var t1=new THREE.Mesh(new THREE.BoxGeometry(len,.05,.32),m);
var t2=t1.clone(); var inima=new THREE.Mesh(new THREE.BoxGeometry(len,.9,.03),m);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true;g.add(p);});
return g;
}
var profil=grinda(12); grupProfil.add(profil);
/* --- TABLA: varianta alternativa de piesa --- */
function facTabla(len,lat,gros,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var pl=new THREE.Mesh(new THREE.BoxGeometry(len,gros,lat),m);
pl.castShadow=true; g.add(pl);
return g;
}
var tablaGrup=new THREE.Group(); tablaGrup.visible=false; grupProfil.add(tablaGrup);
var tablaCorp=facTabla(12,1.5,.06); tablaGrup.add(tablaCorp);
/* gauri in tabla, dispuse pe doua randuri */
var tablaGauri=new THREE.Group(); tablaGauri.visible=false; tablaGrup.add(tablaGauri);
[-5.0,-3.5,-2.0,-0.5,1.0,2.5,4.0,5.3].forEach(function(x){
[-0.45,0.45].forEach(function(z){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.085,.085,.09,12),matGaura);
h.position.set(x,0,z); tablaGauri.add(h);
});
});
/* decupaj dreptunghiular, ca la debitarea de tabla */
[-5.95,5.95].forEach(function(x){
var d=new THREE.Mesh(new THREE.BoxGeometry(.22,.08,.5),matGaura);
d.position.set(x,0,0); tablaGauri.add(d);
});
/* nervuri de rigidizare sudate pe tabla + cordoanele lor */
var tablaNerv=new THREE.Group(); tablaNerv.visible=false; tablaGrup.add(tablaNerv);
var tablaCord=new THREE.Group(); tablaCord.visible=false; tablaGrup.add(tablaCord);
[-4.2,-1.4,1.4,4.2].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.05,.42,1.4),matProfil);
n.position.set(x,.24,0); n.castShadow=true; tablaNerv.add(n);
[.03,-.03].forEach(function(dz){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,1.4,8),matCordon);
c.rotation.x=Math.PI/2; c.position.set(x+dz*1.6,.045,0); tablaCord.add(c);
});
});
/* bordura sudata pe cele doua laturi lungi */
[-.72,.72].forEach(function(z){
var b=new THREE.Mesh(new THREE.BoxGeometry(11.6,.30,.05),matProfil);
b.position.set(0,.18,z); b.castShadow=true; tablaNerv.add(b);
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,11.6,8),matCordon);
c.rotation.z=Math.PI/2; c.position.set(0,.04,z); tablaCord.add(c);
});
/* --- BARE IN PARALEL LA SABLARE ---
Fereastra utila a tunelului are 1.700 x 1.200 mm. Un profil de 306 mm latime
intra de trei ori pe latime, cu spatiu intre bare. La linia de gaurire bratul
de alimentare preia o singura bara, deci barele paralele dispar dupa tunel. */
var N_PARALEL=1, PARALELE=[];
(function(){
[-0.62, 0.62].forEach(function(dz){
var g=new THREE.Group();
var t1=new THREE.Mesh(new THREE.BoxGeometry(12,.05,.32),matProfil);
var t2=t1.clone();
var inima=new THREE.Mesh(new THREE.BoxGeometry(12,.9,.03),matProfil);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true; g.add(p);});
g.position.z=dz; g.visible=false;
grupProfil.add(g);
PARALELE.push(g);
});
})();
/* comutatorul profil / tabla */
var MOD_TABLA=false;
/* --- gauri si frezari, apar dupa modulul de gaurire --- */
var gauri=new THREE.Group(); gauri.visible=false; grupProfil.add(gauri);
[-4.6,-3.1,-1.6,1.6,3.1,4.6].forEach(function(x){
[-0.28,0.28].forEach(function(y){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.075,.075,.06,10),matGaura);
h.rotation.x=Math.PI/2; h.position.set(x,y,0); gauri.add(h);
});
/* gauri si pe talpi */
[.478,-.478].forEach(function(y){
var h2=new THREE.Mesh(new THREE.CylinderGeometry(.06,.06,.07,10),matGaura);
h2.position.set(x,y,.11); gauri.add(h2);
});
});
/* frezari la capete */
[-5.85,5.85].forEach(function(x){
var fr=new THREE.Mesh(new THREE.BoxGeometry(.3,.34,.05),matGaura);
fr.position.set(x,0,0); gauri.add(fr);
});
/* --- subansamblu: traverse, nervuri si cordoane de sudura --- */
var traverse=new THREE.Group(); traverse.visible=false; grupProfil.add(traverse);
[-3.6,3.6].forEach(function(dx){
var t=grinda(3.6); t.rotation.y=Math.PI/2; t.position.set(dx,0,0); traverse.add(t);
});
var nervuri=new THREE.Group(); nervuri.visible=false; grupProfil.add(nervuri);
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.04,.86,.3),matProfil);
n.position.set(x,0,0); n.castShadow=true; nervuri.add(n);
});
var cordoane=new THREE.Group(); cordoane.visible=false; grupProfil.add(cordoane);
function cordon(x,y,z,len,rotY,rotZ){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,len,8),matCordon);
c.rotation.z=Math.PI/2; if(rotY) c.rotation.y=rotY; if(rotZ) c.rotation.z+=rotZ;
c.position.set(x,y,z); cordoane.add(c); return c;
}
/* cordoane la nervuri, pe ambele talpi si pe ambele fete */
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
[.44,-.44].forEach(function(y){
[.15,-.15].forEach(function(z){ cordon(x,y,z,.3,Math.PI/2); });
});
});
/* cordoane la imbinarea traverselor */
[-3.6,3.6].forEach(function(x){
[.44,-.44].forEach(function(y){ cordon(x,y,0,.34,Math.PI/2); });
[.3,-.3].forEach(function(z){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,.9,8),matCordon);
c.position.set(x,0,z); cordoane.add(c);
});
});
/* --- rotator de sudura: piesa se sprijina si se roteste --- */
var rotator=new THREE.Group(); rotator.visible=false; scena.add(rotator);
[-2.6,2.6].forEach(function(dx){
var cadru=new THREE.Group();
var stalpM=new THREE.Mesh(new THREE.BoxGeometry(.34,2.5,.5),mat(0xE8E4DE,{r:.6}));
stalpM.position.set(dx,1.25,-1.5); cadru.add(stalpM);
var stalpS=stalpM.clone(); stalpS.position.set(dx,1.25,1.5); cadru.add(stalpS);
var trav=new THREE.Mesh(new THREE.BoxGeometry(.3,.3,3.4),mat(0xE8E4DE,{r:.6}));
trav.position.set(dx,2.5,0); cadru.add(trav);
var baza=new THREE.Mesh(new THREE.BoxGeometry(.9,.22,3.9),mat(0xB8B2AC,{r:.7}));
baza.position.set(dx,.11,0); cadru.add(baza);
[-1.5,1.5].forEach(function(dz){
var brat=new THREE.Mesh(new THREE.BoxGeometry(.22,.9,.22),mat(0xC0392B,{r:.55}));
brat.position.set(dx,2.05,dz); cadru.add(brat);
});
var lant=new THREE.Mesh(new THREE.TorusGeometry(.95,.055,7,22),mat(0x3A3F46,{m:.5,r:.4}));
lant.rotation.y=Math.PI/2; lant.position.set(dx,1.15,0); cadru.add(lant);
rotator.add(cadru);
});
var lumProces=new THREE.PointLight(0xffffff,0,18); scena.add(lumProces);
/* ================= SCANTEI ================= */
function facePartic(n,culoare,marime){
var g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var m=new THREE.PointsMaterial({color:culoare,size:marime,transparent:true,opacity:.95,
depthWrite:false,blending:THREE.AdditiveBlending});
var pts=new THREE.Points(g,m); pts.visible=false; scena.add(pts);
return {pts:pts,pos:p,vel:new Float32Array(n*3),viata:new Float32Array(n),n:n};
}
var SC_SUD=facePartic(160,0xBFE0FF,.16);
var SC_GAUR=facePartic(120,0xFFC46B,.15);
var SC_SABL=facePartic(220,0xE8D6AE,.10);
function emite(S,origine,forta,imprastiere){
for(var i=0;i<S.n;i++){
if(S.viata[i]<=0&&Math.random()<.22){
S.pos[i*3]=origine.x+(Math.random()-.5)*imprastiere;
S.pos[i*3+1]=origine.y+(Math.random()-.5)*.2;
S.pos[i*3+2]=origine.z+(Math.random()-.5)*imprastiere;
S.vel[i*3]=(Math.random()-.5)*forta;
S.vel[i*3+1]=Math.random()*forta*1.5;
S.vel[i*3+2]=(Math.random()-.5)*forta;
S.viata[i]=.4+Math.random()*.6;
}
}
}
function actualizeazaPartic(S,dt){
var viu=false;
for(var i=0;i<S.n;i++){
if(S.viata[i]>0){
S.viata[i]-=dt; viu=true;
S.vel[i*3+1]-=9.8*dt*.35;
S.pos[i*3]+=S.vel[i*3]*dt; S.pos[i*3+1]+=S.vel[i*3+1]*dt; S.pos[i*3+2]+=S.vel[i*3+2]*dt;
if(S.pos[i*3+1]<.05){S.pos[i*3+1]=.05; S.vel[i*3+1]*=-.35;}
} else { S.pos[i*3+1]=-99; }
}
S.pts.geometry.attributes.position.needsUpdate=true;
S.pts.visible=viu;
}
/* ---------- ETAPE ---------- */
var HL=1.35, HS=0.65;
var ETAPE=[
{n:'Spațiu de depozitare material brut',d:'Profilul HEB de 12 m așteaptă în spațiul de depozitare, lângă poarta de intrare.',
t:5,c:[[6.5,12.8,1.3,'rastel'],[6.5,12.8,1.3,'rastel']]},
{n:'Transfer pe linie',d:'Podul rulant din deschiderea de est așază profilul pe rolele de intrare.',
t:6,c:[[6.5,12.8,1.3,'rastel'],[6.5,AXA,HL,'tunel']],arc:2.6,pod:'E'},
{n:'Sablare în tunel',d:'Șase turbine de 11 kW curăță profilul la SA 2.5 într-o singură trecere.',
t:15,c:[[6.5,AXA,HL,'tunel'],[26,AXA,HL,'tunel']],
cul:function(t){var k=Math.min(Math.max((t*19.5-6)/7.5,0),1);return CUL.rugina.clone().lerp(CUL.sa3,k);},
lum:function(t){var c=6.5+t*19.5;var o=OFU('tunel');
return (c>11&&c<21)?{p:poz(16.25+o.L,AXA+o.W,2.6),c:0xFFC86B,i:2.4+Math.random()*1.6}:null;}},
{n:'Spre linia de găurire',d:'Profilul curățat trece pe masa de intrare, unde brațul de alimentare îl preia.',
t:5,c:[[26,AXA,HL,'tunel'],[40,AXA,HL,'gaurire']]},
{n:'Găurire CNC pe trei fețe',d:'Un ax vertical și două orizontale găuresc inima și tălpile simultan.',
t:10,c:[[40,AXA,HL,'gaurire'],[52,AXA,HL,'gaurire']],
lum:function(t){var c=40+t*12;var o=OFU('gaurire');
return (Math.abs(c-46.5)<5.5&&Math.random()>.4)?{p:poz(48.5+o.L,AXA+o.W,2.1),c:0xFFE9A8,i:3.4}:null;}},
{n:'Debitare cu șanfren',d:'Ferăstrăul cu înclinare taie la 11,95 m, la unghiul cerut de îmbinare.',
t:6,c:[[52,AXA,HL,'gaurire'],[58,AXA,HL,'gaurire']],
lum:function(t){var o=OFU('gaurire');
return (t>.3&&t<.85&&Math.random()>.3)?{p:poz(57.5+o.L,AXA+o.W,1.9),c:0xFFD9A0,i:3.0}:null;}},
{n:'Role de ieșire',d:'Profilul prelucrat ajunge la capătul liniei.',
t:4,c:[[58,AXA,HL,'gaurire'],[67.5,AXA,HL,'gaurire']]},
{n:'Transfer la sudură',d:'Podurile rulante își predau piesa peste spațiul central, din est în vest.',
t:9,c:[[67.5,AXA,HL,'gaurire'],[68.5,31.8,HS,'sud1']],arc:3.6,pod:'EV'},
{n:'Sudura subansamblului',d:'Trei zone de lucru cu MIG/MAG în puls. Rotatoarele întorc piesa fără pod.',
t:9,c:[[68.5,31.8,HS,'sud1'],[68.5,31.8,HS,'sud1']],
lum:function(t){var o=OFU('sud1');
return {p:poz(68.5+o.L+(Math.random()-.5)*4,31.8+o.W,1.4),c:0x9FC6FF,i:Math.random()>.35?4.5:.6};}},
{n:'Transport spre camere',d:'Podul rulant din vest coboară subansamblul în culoarul de manevră.',
t:7,c:[[68.5,31.8,HS,'sud1'],[42,26.3,HS,'camsab']],arc:2.4,pod:'V'},
{n:'Camera de sablare',d:'Cordoanele și tăieturile se curăță înainte de acoperire. Uși pe ambele capete.',
t:8,c:[[42,26.3,HS,'camsab'],[29,26.3,HS,'camsab']],
cul:function(t){return CUL.sa3;},
lum:function(t){var o=OFU('camsab');
return t>.5?{p:poz(29+o.L,26.3+o.W,2.4),c:0xFFC86B,i:2.2+Math.random()*1.4}:null;}},
{n:'Camera de vopsire',d:'Flux longitudinal de aer, uscare forțată. Ieșirea se face prin capătul opus.',
t:10,c:[[29,26.3,HS,'camsab'],[38.2,26.3,HS,'camsab'],[38.2,32.8,HS,'camvops'],[29,32.8,HS,'camvops']],
cul:function(t){var k=Math.min(Math.max((t-.6)/.32,0),1);return CUL.sa3.clone().lerp(CUL.grund,k);},
lum:function(t){var o=OFU('camvops');
return t>.58?{p:poz(29+o.L,32.8+o.W,2.4),c:0xFF8A7A,i:1.8}:null;}},
{n:'Depozitare și expediție',d:'Subansamblul vopsit iese prin poarta a doua. Fluxul în U s-a închis.',
t:9,c:[[29,32.8,HS,'camvops'],[18,32.8,HS,'camvops'],[11,34,HS,'depozit'],[-3,29.35,HS,0]]}
];
function OFU(id){
if(!id||typeof UNITATI==='undefined')return {L:0,W:0};
for(var i=0;i<UNITATI.length;i++) if(UNITATI[i].id===id)
return {L:UNITATI[i].off.x, W:-UNITATI[i].off.z};
return {L:0,W:0};
}
function pctO(w){var o=OFU(w[3]);return [w[0]+o.L,w[1]+o.W,w[2]];}
/* durate reale in minute, derivate din avansul de 1 m/min pe tunel si pe linia de gaurire */
var MIN_REALE=[3,4,20,12,12,5,6,5,90,6,25,20,5];
var T_REAL_TOTAL=MIN_REALE.reduce(function(a,b){return a+b;},0);
var SCARA_MIN=0.34; /* o secunda de animatie = ~3 minute reale */
ETAPE.forEach(function(e,i){ e.tPrezentare=e.t; e.min=MIN_REALE[i]; });
var TIMP_REAL=false;
function aplicaScaraTimp(){
ETAPE.forEach(function(e,i){ e.t = TIMP_REAL ? e.min*SCARA_MIN : e.tPrezentare; });
T_TOTAL=0; praguri=[];
ETAPE.forEach(function(e){ praguri.push([T_TOTAL,T_TOTAL+e.t]); T_TOTAL+=e.t; });
timp=Math.min(timp,T_TOTAL);
}
var T_TOTAL=0, praguri=[];
ETAPE.forEach(function(e){praguri.push([T_TOTAL,T_TOTAL+e.t]);T_TOTAL+=e.t;});
var cont=document.getElementById('progres-etape');
ETAPE.forEach(function(){var d=document.createElement('div');d.className='pas';cont.appendChild(d);});
var pasi=[].slice.call(cont.children);
function caleP(cale,t,arc){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
var p=poz(a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt, a[2]+(b[2]-a[2])*lt);
if(arc) p.y+=Math.sin(t*Math.PI)*arc;
return p;
}
function caleLW(cale,t){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
return [a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt];
}
/* ---------- animatie ---------- */
var timp=0, ruleaza=false, urmarire=false;
var btnPlay=document.getElementById('btn-play'), scrub=document.getElementById('scrub'),
viteza=document.getElementById('viteza'), btnUrm=document.getElementById('btn-urmarire');
function miscaPod(pod,Lt,Wt,activ,dt){
var u=pod.userData, k=Math.min(dt*2.2,1);
u.L+=(Lt-u.L)*k; u.Wt+=(Wt-u.Wt)*k;
var Wc=Math.min(Math.max(u.Wt,u.W1+1),u.W2-1);
pod.position.copy(poz(u.L,(u.W1+u.W2)/2,Y_POD));
u.trolley.position.z=(HALA_W/2-Wc)-pod.position.z;
return {L:u.L,W:Wc};
}
function cabluriLa(pod,pTinta){
var u=pod.userData;
var tr=new THREE.Vector3(); u.trolley.getWorldPosition(tr); tr.y-=0.35;
traversa.visible=STRAT_ON.poduri; traversa.position.set(pTinta.x,pTinta.y+.75,pTinta.z);
var pcte=[[tr,new THREE.Vector3(pTinta.x,pTinta.y+.75,pTinta.z)],
[new THREE.Vector3(pTinta.x-3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x-3.5,pTinta.y+.55,pTinta.z)],
[new THREE.Vector3(pTinta.x+3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x+3.5,pTinta.y+.55,pTinta.z)],
[tr,tr]];
for(var i=0;i<4;i++){
var g=cabluri[i].geometry;
g.setFromPoints(pcte[i]); cabluri[i].visible=STRAT_ON.poduri;
}
}
function ascundeCabluri(){cabluri.forEach(function(c){c.visible=false;});traversa.visible=false;}
var idxCur=0;
function aplica(dt){
var idx=-1;
for(var i=0;i<ETAPE.length;i++) if(timp>=praguri[i][0]&&timp<praguri[i][1]){idx=i;break;}
if(idx<0) idx=ETAPE.length-1;
idxCur=idx;
var e=ETAPE[idx], t=Math.min(Math.max((timp-praguri[idx][0])/e.t,0),1);
var P=caleP(e.c,t,e.arc);
grupProfil.position.copy(P);
traverse.visible = !MOD_TABLA && (idx>8 || (idx===8&&t>.5));
/* --- culoarea corpului --- */
var cul;
if(e.cul) cul=e.cul(t);
else if(idx<2) cul=CUL.rugina;
else if(idx<11) cul=CUL.sa3;
else cul=CUL.grund;
/* fara shop primer, otelul curatat se oxideaza inapoi intre sablare si vopsire */
if(RUST_BACK && idx>=4 && idx<12){
var kr=Math.min((idx-3)/6,1)*0.85;
cul=cul.clone().lerp(CUL_rustBack,kr);
}
matProfil.color.copy(cul); matProfil.emissive.copy(cul);
/* --- gaurile apar in timpul modulului de gaurire --- */
var gaurit = (idx>4) || (idx===4 && t>.45);
var sudat = (idx>8) || (idx===8 && t>.35);
/* barele paralele exista doar pana la intrarea in linia de gaurire (etapele 0-3) */
var inSablare = idx<=3;
PARALELE.forEach(function(g,k){
g.visible = (!MOD_TABLA) && inSablare && (N_PARALEL >= k+2);
});
if(MOD_TABLA){
profil.visible=false; gauri.visible=false;
traverse.visible=false; nervuri.visible=false; cordoane.visible=false;
tablaGrup.visible=true;
tablaGauri.visible=gaurit;
tablaNerv.visible=sudat; tablaCord.visible=sudat;
} else {
tablaGrup.visible=false;
profil.visible=true;
gauri.visible=gaurit;
traverse.visible=sudat; nervuri.visible=sudat; cordoane.visible=sudat;
}
/* --- culoarea cordoanelor: brute, apoi curatate, apoi grunduite --- */
if(idx<11) matCordon.color.copy(CUL.cordon);
else if(idx===11){
var kc=Math.min(Math.max((t-.35)/.4,0),1);
matCordon.color.copy(CUL.cordon.clone().lerp(CUL.sa3,kc));
} else if(idx===12){
var kg=Math.min(Math.max((t-.6)/.32,0),1);
var baza=RUST_BACK?CUL_rustBack:CUL.sa3;
matCordon.color.copy(baza.clone().lerp(CUL.grund,kg));
} else matCordon.color.copy(CUL.grund);
/* --- rotatorul de sudura --- */
rotator.visible=(idx===8);
if(idx===8){
rotator.position.copy(P); rotator.position.y=0;
var unghi=Math.sin(performance.now()/1000*.55)*1.15;
grupProfil.rotation.x=unghi;
} else if(idx>8&&idx<12){ grupProfil.rotation.x*=0.92; }
else grupProfil.rotation.x=0;
/* particule pe procese */
if(idx===2){var ot=OFU('tunel'); if(6.5+t*19.5>11&&6.5+t*19.5<21) emite(SC_SABL,poz(16.25+ot.L,AXA+ot.W,1.9),2.2,1.6);}
if(idx===4||idx===5) emite(SC_GAUR,P.clone().setY(P.y+.1),3.4,.5);
if(idx===8) emite(SC_SUD,P.clone().setY(P.y+.35),4.2,1.4);
var l=e.lum?e.lum(t):null;
if(l){lumProces.intensity=l.i;lumProces.color.setHex(l.c);lumProces.position.copy(l.p);}
else lumProces.intensity=0;
// poduri
var lw=caleLW(e.c,t);
ascundeCabluri();
var carE={L:8,W:8}, carV={L:45,W:38};
if(e.pod==='E'){carE={L:lw[0],W:lw[1]};}
else if(e.pod==='V'){carV={L:lw[0],W:lw[1]};}
else if(e.pod==='EV'){ if(lw[1]<20.5) carE={L:lw[0],W:lw[1]}; else carV={L:lw[0],W:lw[1]}; }
var pe=miscaPod(podE,carE.L,carE.W,true,dt||.016);
var pv=miscaPod(podV,carV.L,carV.W,true,dt||.016);
if(e.pod==='E') cabluriLa(podE,P);
if(e.pod==='V') cabluriLa(podV,P);
if(e.pod==='EV') cabluriLa(lw[1]<20.5?podE:podV,P);
// brat alimentare urmareste profilul pe linia de gaurire
var og=OFU('gaurire');
var Lc=Math.min(Math.max(lw[0]-6,33+og.L),59.5+og.L);
if(idx>=3&&idx<=6) carucior.position.copy(poz(Lc,6.5+og.W,1.0));
var TA=performance.now()/1000;
// turbine se invart cand tunelul lucreaza
var tunelActiv=(idx===2);
ANIM.turbine.forEach(function(d){ if(tunelActiv) d.rotation.y+=0.9; });
if(ANIM.perdea) ANIM.perdea.material.opacity=tunelActiv?(.10+Math.random()*.12):0;
// axele de gaurire lucreaza cand piesa e sub portal
var gaurire=(idx===4);
if(ANIM.axV){
ANIM.axV.position.y=ANIM.axVy+(gaurire?(-0.35+0.35*Math.abs(Math.sin(TA*3))):0);
var c1=Math.sin(TA*2.6), c2=Math.sin(TA*2.6+1.5);
ANIM.axH1.position.z=ANIM.axH1z+(gaurire? -0.3*Math.max(0,c1):0);
ANIM.axH2.position.z=ANIM.axH2z+(gaurire? 0.3*Math.max(0,c2):0);
if(gaurire){ANIM.axV.rotation.y+=1.1;}
}
// sudorii din zona 1 lucreaza in etapa de sudura
var sudActiv=(idx===8);
pist1.visible=sudActiv&&STRAT_ON.operatori; pist2.visible=sudActiv&&STRAT_ON.operatori;
if(sudActiv){
var s1=sudori[0][0], s2=sudori[0][1];
s1.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2);
s2.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2+1);
s1.tors.rotation.x=.18; s2.tors.rotation.x=.18;
var h1=new THREE.Vector3(); s1.bd.getWorldPosition(h1);
var h2=new THREE.Vector3(); s2.bd.getWorldPosition(h2);
pist1.position.set(h1.x+.15,h1.y-.1,h1.z-(0.35));
pist2.position.set(h2.x-.15,h2.y-.1,h2.z+(0.35));
pist1.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist2.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist1.userData.varf.material.transparent=true; pist2.userData.varf.material.transparent=true;
} else {
sudori[0][0].bd.rotation.x=0; sudori[0][1].bd.rotation.x=0;
sudori[0][0].tors.rotation.x=0; sudori[0][1].tors.rotation.x=0;
}
// sablatorul lucreaza cand piesa e in camera
var sabActiv=(idx===10&&t>.35);
jetSab.visible=sabActiv&&STRAT_ON.operatori; furtunSab.visible=(idx===10)&&STRAT_ON.operatori;
if(sabActiv){
var bal=Math.sin(TA*1.4)*2.6;
var ocs=OFU('camsab');
opCamSab.g.position.copy(poz(29+bal+ocs.L,27.7+ocs.W,0));
opCamSab.bd.rotation.x=-1.25;
jetSab.position.copy(poz(29+bal+ocs.L,27.0+ocs.W,1.35));
jetSab.rotation.x=Math.PI/2+.25;
jetSab.material.opacity=.18+Math.random()*.15;
furtunSab.position.copy(poz(29+bal+1.5+ocs.L,28.6+ocs.W,.8));
furtunSab.rotation.z=1.2; furtunSab.rotation.y=.4;
} else opCamSab.bd.rotation.x=0;
// vopsitorul lucreaza in etapa de vopsire
var vopsActiv=(idx===11&&t>.55);
jetVops.visible=vopsActiv&&STRAT_ON.operatori;
if(vopsActiv){
var bal2=Math.sin(TA*1.1)*3.0;
var ocv=OFU('camvops');
opVops.g.position.copy(poz(29+bal2+ocv.L,33.9+ocv.W,0));
opVops.bd.rotation.x=-1.2+0.15*Math.sin(TA*3);
jetVops.position.copy(poz(29+bal2+ocv.L,33.35+ocv.W,1.3));
jetVops.rotation.x=Math.PI/2+.2;
jetVops.material.opacity=.22+Math.random()*.14;
} else opVops.bd.rotation.x=0;
if(TIMP_REAL){
var scurs=0; for(var q=0;q<idx;q++) scurs+=ETAPE[q].min;
scurs+=ETAPE[idx].min*t;
document.getElementById('et-timp').textContent=
'minutul '+Math.round(scurs)+' din '+T_REAL_TOTAL+' · etapa durează '+ETAPE[idx].min+' min';
document.getElementById('et-timp').style.display='block';
} else document.getElementById('et-timp').style.display='none';
document.getElementById('et-nr').textContent=idx+1;
document.getElementById('et-nume').textContent=e.n;
document.getElementById('et-desc').textContent=e.d;
pasi.forEach(function(p,i){p.className='pas'+(i<idx?' facut':i===idx?' acum':'');});
scrub.value=Math.round(timp/T_TOTAL*1000);
}
/* ---------- camera orbit ---------- */
var tinta=new THREE.Vector3(0,1,0), raza=105, az=1.25, el=.80;
function pozCamera(){
var baza=urmarire?grupProfil.position.clone():tinta;
if(raza<26) raza=26;
if(el<.52) el=.52;
if(typeof MOBIL!=='undefined'&&MOBIL){ if(raza<34) raza=34; }
camera.position.set(
baza.x+raza*Math.cos(el)*Math.cos(az),
Math.max(baza.y+raza*Math.sin(el),6),
baza.z+raza*Math.cos(el)*Math.sin(az));
camera.lookAt(baza);
}
pozCamera();
var drag=null;
rend.domElement.addEventListener('contextmenu',function(e){e.preventDefault();});
rend.domElement.addEventListener('pointerdown',function(e){
if(MUTARE&&e.button===0){var u=alegeUnitate(e); if(u){dragUnit=u; return;}}
drag={x:e.clientX,y:e.clientY,b:e.button};});
addEventListener('pointerup',function(){drag=null; if(dragUnit){dragUnit=null; toast(false); ascundeCote();} });
addEventListener('pointermove',function(e){
if(dragUnit){mutaUnitate(e); return;}
if(!drag)return;
var dx=e.clientX-drag.x, dy=e.clientY-drag.y; drag.x=e.clientX; drag.y=e.clientY;
if(drag.b===2||e.shiftKey){
var dir=new THREE.Vector3().subVectors(tinta,camera.position); dir.y=0; dir.normalize();
var lat=new THREE.Vector3(-dir.z,0,dir.x);
tinta.addScaledVector(lat,-dx*raza*.0012).addScaledVector(dir,dy*raza*.0012);
urmarire=false; btnUrm.classList.remove('activ');
}else{
az+=dx*.005; el=Math.min(Math.max(el+dy*.004,.12),1.45);
}
});
rend.domElement.addEventListener('wheel',function(e){
raza=Math.min(Math.max(raza*(1+e.deltaY*.001),16),220);
},{passive:true});
btnPlay.onclick=function(){
if(timp>=T_TOTAL){timp=0;}
ruleaza=!ruleaza; btnPlay.textContent=ruleaza?'Pauză':'Pornire';
};
scrub.oninput=function(){timp=scrub.value/1000*T_TOTAL;aplica(.016);};
btnUrm.onclick=function(){urmarire=!urmarire;btnUrm.classList.toggle('activ',urmarire);if(urmarire)raza=Math.min(raza,42);};
document.getElementById('btn-camera').onclick=function(){
urmarire=false;btnUrm.classList.remove('activ');
tinta.set(0,1,0);raza=105;az=1.25;el=.80;
};
/* ================= TUR CINEMATIC ================= */
var TUR=false, turT=0;
var CADRE=[
{d:5, az:1.05, el:.50, r:130,tinta:[0,1,0], txt:'Linie completă de producție'},
{d:6, az:1.25, el:.62, r:95, tinta:[0,1,0], txt:'Hala de 42,80 x 74,80 m'},
{d:7, az:2.05, el:.30, r:34, tinta:[-22,2,16], txt:'Intrarea materialului brut și tunelul de sablare'},
{d:7, az:0.55, el:.22, r:26, tinta:[10,2,16], txt:'Linia de găurire CNC și debitarea cu șanfren'},
{d:6, az:-0.5, el:.34, r:38, tinta:[28,2,-8], txt:'Zonele de sudură și exhaustarea'},
{d:7, az:1.9, el:.26, r:30, tinta:[-8,2,-8], txt:'Camerele de sablare și vopsire'},
{d:6, az:1.25, el:.85, r:78, tinta:[0,1,0], txt:'Fluxul complet, în formă de U'},
{d:5, az:1.55, el:.55, r:105,tinta:[0,1,0], txt:'Fluxul în U, de la poarta de intrare la cea de ieșire'}
];
var TUR_TOTAL=0; CADRE.forEach(function(c){TUR_TOTAL+=c.d;});
/* pe ecran vertical camera trebuie sa stea mai departe si mai sus,
altfel pardoseala umple cadrul */
CADRE.forEach(function(c){ c.el=Math.max(c.el,0.58); c.r=Math.max(c.r,42); });
if(typeof MOBIL!=='undefined'&&MOBIL){
CADRE.forEach(function(c){ c.r=c.r*1.6; });
}
function lerp(a,b,k){return a+(b-a)*k;}
function ease(k){return k<.5?4*k*k*k:1-Math.pow(-2*k+2,3)/2;}
function ruleazaTur(dt){
turT+=dt;
if(turT>=TUR_TOTAL){opresteTur();return;}
var acc=0,i=0;
for(i=0;i<CADRE.length;i++){ if(turT<acc+CADRE[i].d) break; acc+=CADRE[i].d; }
var c=CADRE[i], cNext=CADRE[Math.min(i+1,CADRE.length-1)];
var k=ease(Math.min((turT-acc)/c.d,1));
az=lerp(c.az,cNext.az,k); el=lerp(c.el,cNext.el,k); raza=lerp(c.r,cNext.r,k);
tinta.set(lerp(c.tinta[0],cNext.tinta[0],k),lerp(c.tinta[1],cNext.tinta[1],k),lerp(c.tinta[2],cNext.tinta[2],k));
var el2=document.getElementById('turTxt');
el2.textContent=c.txt; el2.style.opacity=(k<.12?k/.12:(k>.88?(1-k)/.12:1));
}
var TUR_pereti=false;
function opresteTur(){
TUR=false; document.getElementById('turBara').style.display='none';
if(TUR_pereti){ var cb=document.querySelector('#straturi input[data-strat=pereti]');
if(cb){ cb.checked=true; cb.dispatchEvent(new Event('change')); } TUR_pereti=false; }
document.getElementById('etapa').style.display='block';
document.getElementById('btn-tur').classList.remove('activ');
urmarire=false;
}
document.getElementById('btn-tur').onclick=function(){
TUR=!TUR; this.classList.toggle('activ',TUR);
document.getElementById('turBara').style.display=TUR?'block':'none';
document.getElementById('etapa').style.display=TUR?'none':'block';
if(TUR){turT=0; timp=0; ruleaza=true; btnPlay.textContent='Pauză'; viteza.value=170; urmarire=false;
btnUrm.classList.remove('activ');
/* in filmulet peretii incurca privirea - se sting automat */
var cbP=document.querySelector('#straturi input[data-strat=pereti]');
if(cbP&&cbP.checked){ cbP.checked=false; cbP.dispatchEvent(new Event('change')); TUR_pereti=true; }}
};
/* ================= ADAPTARE MOBIL ================= */
var MOBIL = (innerWidth<900) || /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if(MOBIL){
rend.setPixelRatio(Math.min(devicePixelRatio,1.5));
rend.shadowMap.enabled=false;
soare.castShadow=false;
scena.traverse(function(o){ if(o.isMesh){o.castShadow=false;o.receiveShadow=false;} });
/* taie sursele de lumina scumpe: pastram doar 4 lampi */
var pcte=[]; scena.children.forEach(function(o){ if(o.isPointLight&&o!==lumProces) pcte.push(o); });
pcte.forEach(function(o,i){ if(i%3!==0) scena.remove(o); });
/* mai putin praf */
if(ANIM.praf) ANIM.praf.material.size=.12;
raza=150; el=.82;
}
document.getElementById('btn-straturi').onclick=function(){
document.getElementById('straturi').classList.toggle('deschis');
this.classList.toggle('activ');
};
/* ---- gesturi tactile: un deget roteste, doua degete zoom + pan ---- */
(function(){
var el0=rend.domElement, atins=[], dPrec=0;
function dist(a,b){var dx=a.clientX-b.clientX,dy=a.clientY-b.clientY;return Math.hypot(dx,dy);}
el0.addEventListener('touchstart',function(e){
atins=[].slice.call(e.touches);
if(atins.length===2) dPrec=dist(atins[0],atins[1]);
if(MUTARE&&atins.length===1){
var u=alegeUnitate({clientX:atins[0].clientX,clientY:atins[0].clientY});
if(u){dragUnit=u;}
}
},{passive:true});
el0.addEventListener('touchmove',function(e){
var t=[].slice.call(e.touches);
if(dragUnit&&t.length===1){
e.preventDefault();
mutaUnitate({clientX:t[0].clientX,clientY:t[0].clientY}); atins=t; return;
}
if(t.length===1&&atins.length===1){
e.preventDefault();
var dx=t[0].clientX-atins[0].clientX, dy=t[0].clientY-atins[0].clientY;
az+=dx*.006; el=Math.min(Math.max(el+dy*.005,.12),1.45);
} else if(t.length===2&&atins.length===2){
e.preventDefault();
var d=dist(t[0],t[1]);
raza=Math.min(Math.max(raza*(dPrec/d),16),260); dPrec=d;
var mx=((t[0].clientX+t[1].clientX)/2)-((atins[0].clientX+atins[1].clientX)/2);
var my=((t[0].clientY+t[1].clientY)/2)-((atins[0].clientY+atins[1].clientY)/2);
var dir=new THREE.Vector3().subVectors(tinta,camera.position); dir.y=0; dir.normalize();
var lat=new THREE.Vector3(-dir.z,0,dir.x);
tinta.addScaledVector(lat,-mx*raza*.0016).addScaledVector(dir,my*raza*.0016);
urmarire=false; btnUrm.classList.remove('activ');
}
atins=t;
},{passive:false});
el0.addEventListener('touchend',function(e){
atins=[].slice.call(e.touches);
if(dragUnit&&atins.length===0){dragUnit=null;toast(false);ascundeCote();}
},{passive:true});
})();
/* ---- pastreaza cadrul: reduce FOV pe ecran ingust ---- */
function adapteazaCamera(){
camera.fov = (innerWidth/innerHeight < 1) ? 52 : 48;
camera.updateProjectionMatrix();
}
adapteazaCamera();
document.getElementById('lumina').addEventListener('input',function(){
var k=this.value/100;
rend.toneMappingExposure=0.95*k;
scena.traverse(function(o){
if(o.isAmbientLight) o.intensity=0.5*k;
if(o.isHemisphereLight) o.intensity=0.4*k;
if(o.isDirectionalLight) o.intensity=(o===soare?1.6:0.3)*k;
});
});
/* ============ FLUX CONTINUU: mai multe piese simultan ============ */
var FLUX_MULTI=false, FANTOME=[];
(function(){
for(var i=0;i<3;i++){
var g=new THREE.Group();
var m=new THREE.MeshStandardMaterial({color:0x8E4B30,roughness:.9,metalness:.15,
transparent:true,opacity:.72});
m.emissive=new THREE.Color(0x8E4B30); m.emissiveIntensity=.16;
var t1=new THREE.Mesh(new THREE.BoxGeometry(12,.05,.32),m);
var t2=t1.clone(); var inima=new THREE.Mesh(new THREE.BoxGeometry(12,.9,.03),m);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){g.add(p);});
g.visible=false; scena.add(g);
FANTOME.push({g:g,mat:m,dec:(i+1)*0.22});
}
})();
function culoareLaMoment(tt){
var idx=-1,acc=0;
for(var i=0;i<ETAPE.length;i++){ if(tt<praguri[i][1]){idx=i;break;} }
if(idx<0) idx=ETAPE.length-1;
if(idx<2) return CUL.rugina;
if(idx<11) return CUL.sa3;
return CUL.grund;
}
function pozLaMoment(tt){
var idx=-1;
for(var i=0;i<ETAPE.length;i++){ if(tt>=praguri[i][0]&&tt<praguri[i][1]){idx=i;break;} }
if(idx<0) idx=ETAPE.length-1;
var e=ETAPE[idx], t=Math.min(Math.max((tt-praguri[idx][0])/e.t,0),1);
return caleP(e.c,t,e.arc);
}
function actualizeazaFantome(){
FANTOME.forEach(function(f){
if(!FLUX_MULTI){f.g.visible=false;return;}
var tt=timp - f.dec*T_TOTAL;
if(tt<0) tt+=T_TOTAL;
f.g.visible=true;
f.g.position.copy(pozLaMoment(tt));
var c=culoareLaMoment(tt); f.mat.color.copy(c); f.mat.emissive.copy(c);
});
}
document.getElementById('btn-multi').onclick=function(){
FLUX_MULTI=!FLUX_MULTI; this.classList.toggle('activ',FLUX_MULTI);
if(FLUX_MULTI){ ruleaza=true; btnPlay.textContent='Pauză';
toast('Flux continuu: patru piese în linie simultan');
setTimeout(function(){toast(false);},3000);
} else { toast(false); }
actualizeazaFantome();
};
document.getElementById('btn-timp').onclick=function(){
TIMP_REAL=!TIMP_REAL; this.classList.toggle('activ',TIMP_REAL);
aplicaScaraTimp();
document.getElementById('etapa').classList.toggle('real',TIMP_REAL);
if(TIMP_REAL){
toast('Durate reale: 3 h 33 min pe subansamblu. Sudura ocupă 90 min din total.');
setTimeout(function(){toast(false);},5000);
}
};
/* ============ RUST-BACK: demonstratia lipsei de shop primer ============ */
var RUST_BACK=false;
var CUL_rustBack=new THREE.Color(0x9A6448); /* oxidare superficiala reaparuta */
/* butonul de shop primer a fost eliminat - RUST_BACK ramane inactiv */
/* ============ BARE IN PARALEL LA SABLARE ============ */
var btnPar=document.getElementById('btn-paralel');
btnPar.onclick=function(){
if(MOD_TABLA){
toast('Tabla de 1.500 mm ocupă singură fereastra tunelului. Comută pe Profil pentru profile în paralel.');
setTimeout(function(){toast(false);},4000);
return;
}
N_PARALEL = N_PARALEL>=3 ? 1 : N_PARALEL+1;
this.textContent = 'Sablare: '+N_PARALEL+(N_PARALEL===1?' profil':' profile');
this.classList.toggle('activ', N_PARALEL>1);
if(N_PARALEL>1){
toast(N_PARALEL+' profile în paralel prin tunel. Fereastra de 1.700 x 1.200 mm le preia simultan; capacitatea de sablare se multiplică, iar linia de găurire preia apoi câte un profil.');
} else {
toast('Un singur profil prin tunel.');
}
setTimeout(function(){toast(false);},5000);
};
/* ============ COMUTARE PROFIL / TABLA ============ */
document.getElementById('btn-tabla').onclick=function(){
MOD_TABLA=!MOD_TABLA; this.classList.toggle('activ',MOD_TABLA);
this.textContent = MOD_TABLA ? 'Profil' : 'Tablă';
if(MOD_TABLA){ N_PARALEL=1; btnPar.textContent='Sablare: 1 profil'; btnPar.classList.remove('activ'); }
if(MOD_TABLA){
toast('Tablă: 12.000 x 1.500 mm. Același flux: sablare, debitare și găurire, sudură, finisare.');
} else {
toast('Profil laminat: grindă de 12 m, până la HEB / HEM 1000.');
}
setTimeout(function(){toast(false);},5000);
};
/* ============ INCARCAREA MODULELOR / CAPACITATE ============ */
var MODULE_CAP=[
{n:'Sablare în tunel', min:20},
{n:'Găurire și debitare',min:17},
{n:'Sudură', min:90},
{n:'Camera de sablare', min:25},
{n:'Camera de vopsire', min:20}
];
var SCENARIU=100; /* tone pe luna */
function randeazaContor(){
/* subansamble/luna estimate la 0,72 t bucata */
var buc=SCENARIU/0.72, oreDisp=8*20*60; /* minute disponibile pe luna, un schimb */
var h='';
MODULE_CAP.forEach(function(m){
var incarcare=buc*m.min/oreDisp*100;
var cul = incarcare>95?'#E74C3C' : (incarcare>70?'#E0A63C':'#2ECC71');
h+='<div style="margin:6px 0">'
+'<div style="display:flex;justify-content:space-between;font-size:11.5px">'
+'<span>'+m.n+'</span><b style="color:'+cul+'">'+Math.round(incarcare)+'%</b></div>'
+'<div style="height:5px;background:rgba(234,228,220,.14);border-radius:3px;margin-top:3px">'
+'<div style="height:5px;width:'+Math.min(incarcare,100)+'%;background:'+cul+';border-radius:3px"></div>'
+'</div></div>';
});
document.getElementById('contorLista').innerHTML=h;
document.getElementById('contorScenariu').textContent=SCENARIU+' t/lună';
}
document.getElementById('btn-200').onclick=function(){
SCENARIU = (SCENARIU===100)?200:100;
this.classList.toggle('activ',SCENARIU===200);
this.textContent = (SCENARIU===100)?'Test 200 t/lună':'Înapoi la 100 t/lună';
document.getElementById('contor').style.display='block';
randeazaContor();
if(SCENARIU===200){
toast('La dublarea producției, sudura devine gâtuire. Restul liniei are rezervă.');
setTimeout(function(){toast(false);},5500);
}
};
document.getElementById('btn-timp').addEventListener('click',function(){
document.getElementById('contor').style.display=TIMP_REAL?'block':'none';
if(TIMP_REAL) randeazaContor();
});
/* ============ CAPTURA DE IMAGINE ============ */
document.getElementById('btn-foto').onclick=function(){
rend.render(scena,camera);
var a=document.createElement('a');
a.href=rend.domElement.toDataURL('image/png');
a.download='linie_structuri_metalice_'+Date.now()+'.png'; a.click();
toast('Imagine salvată în Downloads'); setTimeout(function(){toast(false);},2200);
};
var ceas=performance.now();
function bucla(){
requestAnimationFrame(bucla);
var acum=performance.now(), dt=(acum-ceas)/1000;
if(MOBIL && dt<0.028) return;
ceas=acum;
if(TUR) ruleazaTur(dt);
if(ruleaza){
timp+=dt*(viteza.value/100);
if(timp>=T_TOTAL){timp=T_TOTAL;ruleaza=false;btnPlay.textContent='Reluare';}
}
actualizeazaPartic(SC_SUD,dt); actualizeazaPartic(SC_GAUR,dt); actualizeazaPartic(SC_SABL,dt);
/* pulsatie subtila lampi + plutire praf */
var tt=acum/1000;
if(ANIM.praf){
var a=ANIM.praf.geometry.attributes.position, b=ANIM.prafBase;
for(var pi=0;pi<a.count;pi++){
a.array[pi*3+1]=b[pi*3+1]+Math.sin(tt*.35+pi)*.28;
a.array[pi*3]=b[pi*3]+Math.sin(tt*.22+pi*.7)*.22;
}
a.needsUpdate=true;
}
aplica(dt); actualizeazaFantome(); pozCamera(); rend.render(scena,camera);
}
bucla();
/* ================= MODELE GLB REALE ================= */
function parseGLB(buf){
var dv=new DataView(buf), total=dv.getUint32(8,true), off=12, js=null, bin=null;
while(off<total){
var cl=dv.getUint32(off,true), ct=dv.getUint32(off+4,true);
if(ct===0x4E4F534A) js=JSON.parse(new TextDecoder().decode(new Uint8Array(buf,off+8,cl)));
else if(ct===0x004E4942) bin=buf.slice(off+8,off+8+cl);
off+=8+cl;
}
return {js:js,bin:bin};
}
function accData(js,bin,ai){
var a=js.accessors[ai], bv=js.bufferViews[a.bufferView];
var cs={5121:1,5123:2,5125:4,5126:4}[a.componentType];
var nc={SCALAR:1,VEC2:2,VEC3:3,VEC4:4}[a.type];
var stride=bv.byteStride||cs*nc, base=(bv.byteOffset||0)+(a.byteOffset||0);
var Arr={5121:Uint8Array,5123:Uint16Array,5125:Uint32Array,5126:Float32Array}[a.componentType];
var out=new Arr(a.count*nc), dvb=new DataView(bin);
var get={5126:function(o){return dvb.getFloat32(o,true)},5125:function(o){return dvb.getUint32(o,true)},
5123:function(o){return dvb.getUint16(o,true)},5121:function(o){return dvb.getUint8(o)}}[a.componentType];
for(var i=0;i<a.count;i++) for(var c=0;c<nc;c++) out[i*nc+c]=get(base+i*stride+c*cs);
return out;
}
function buildGLB(buf){
var P=parseGLB(buf), js=P.js, bin=P.bin;
var texCache={};
function tex(ti){
if(texCache[ti]) return texCache[ti];
var img=js.images[js.textures[ti].source], bv=js.bufferViews[img.bufferView];
var blob=new Blob([new Uint8Array(bin,bv.byteOffset||0,bv.byteLength)],{type:img.mimeType});
var t=new THREE.TextureLoader().load(URL.createObjectURL(blob));
t.flipY=false; t.wrapS=t.wrapT=THREE.RepeatWrapping;
texCache[ti]=t; return t;
}
var mats=(js.materials||[]).map(function(m){
var p=m.pbrMetallicRoughness||{};
var mm=new THREE.MeshStandardMaterial({
metalness:Math.min(p.metallicFactor!==undefined?p.metallicFactor:0.1,0.5),
roughness:p.roughnessFactor!==undefined?p.roughnessFactor:0.85,
side:m.doubleSided?THREE.DoubleSide:THREE.FrontSide});
if(p.baseColorFactor) mm.color.setRGB(p.baseColorFactor[0],p.baseColorFactor[1],p.baseColorFactor[2]);
if(p.baseColorTexture) mm.map=tex(p.baseColorTexture.index);
return mm;
});
var meshes=js.meshes.map(function(m){
var g=new THREE.Group();
m.primitives.forEach(function(pr){
var geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.BufferAttribute(accData(js,bin,pr.attributes.POSITION),3));
if(pr.attributes.NORMAL!==undefined) geo.setAttribute('normal',new THREE.BufferAttribute(accData(js,bin,pr.attributes.NORMAL),3));
if(pr.attributes.TEXCOORD_0!==undefined) geo.setAttribute('uv',new THREE.BufferAttribute(accData(js,bin,pr.attributes.TEXCOORD_0),2));
if(pr.indices!==undefined) geo.setIndex(new THREE.BufferAttribute(accData(js,bin,pr.indices),1));
if(pr.attributes.NORMAL===undefined) geo.computeVertexNormals();
var mesh=new THREE.Mesh(geo, pr.material!==undefined?mats[pr.material]:new THREE.MeshStandardMaterial());
mesh.castShadow=true; mesh.receiveShadow=true; g.add(mesh);
});
return g;
});
var nodes=js.nodes.map(function(n){
var g=new THREE.Group();
if(n.matrix){var mtx=new THREE.Matrix4().fromArray(n.matrix);mtx.decompose(g.position,g.quaternion,g.scale);}
else{
if(n.translation) g.position.fromArray(n.translation);
if(n.rotation) g.quaternion.fromArray(n.rotation);
if(n.scale) g.scale.fromArray(n.scale);
}
if(n.mesh!==undefined) g.add(meshes[n.mesh]);
return g;
});
js.nodes.forEach(function(n,i){(n.children||[]).forEach(function(c){nodes[i].add(nodes[c]);});});
var root=new THREE.Group();
js.scenes[js.scene||0].nodes.forEach(function(i){root.add(nodes[i]);});
return root;
}
function incarca(id){
return new Promise(function(res,rej){
try{
var b64=document.getElementById(id).textContent.replace(/\s/g,'');
var bin=atob(b64), n=bin.length, buf=new ArrayBuffer(n), v=new Uint8Array(buf);
for(var i=0;i<n;i++) v[i]=bin.charCodeAt(i);
res(buildGLB(buf));
}catch(e){rej(e);}
});
}
function aseaza(root,inaltime,peLatime){ /* normalizeaza: sta pe sol, centrat, la inaltimea data */
var w=new THREE.Group(); w.add(root);
var box=new THREE.Box3().setFromObject(root), size=new THREE.Vector3(); box.getSize(size);
var ref=peLatime?Math.max(size.x,size.z):size.y;
root.scale.setScalar(inaltime/ref);
box.setFromObject(root);
var c=new THREE.Vector3(); box.getCenter(c);
root.position.set(-c.x,-box.min.y,-c.z);
/* daca axul lung e pe X, roteste pe Z (deschiderea podului e pe Z) */
if(peLatime&&size.x>size.z) w.rotation.y=Math.PI/2;
return w;
}
new Promise(function(){}).then(function(model){
var s1=aseaza(model,1.78,false);
var s2=aseaza(buildGLB_cloneHack(model),1.78,false);
function pozSudor(w,L,W,rot){w.position.copy(poz(L,W,0));w.rotation.y+=rot;w.userData.strat='operatori';scena.add(w);inregistreazaStrat(w);if(typeof adaugaLaUnitate==='function')adaugaLaUnitate(w);}
pozSudor(s1,66.8,30.6,Math.PI*0.15);
pozSudor(s2,70.2,33.0,Math.PI*1.1);
sudori[0][0].g.visible=false; sudori[0][1].g.visible=false;
}).catch(function(e){console.warn('sudor glb:',e);});
function buildGLB_cloneHack(m){return m.clone(true);}
/* ================= STRATURI VIZIBILE ================= */
var STRATURI={hala:[],pereti:[],sablare:[],gaurire:[],sudura:[],camere:[],poduri:[],operatori:[],etichete:[]};
function inregistreazaStrat(o){
var st=o.userData&&o.userData.strat; if(!st||!STRATURI[st])return;
STRATURI[st].push(o); if(!STRAT_ON[st]) o.visible=false;
}
(function(){
[podE,podV,traversa].concat(cabluri).forEach(function(o){o.userData.strat='poduri';});
carucior.userData.strat='gaurire';
podea.userData.strat='hala';
[opSab,opGaur,opCamSab,opVops].forEach(function(o){o.g.userData.strat='operatori';});
sudori.forEach(function(pr){pr.forEach(function(o){o.g.userData.strat='operatori';});});
[pist1,pist2,jetSab,jetVops,furtunSab].forEach(function(o){o.userData.strat='operatori';});
if(ANIM.perdea) ANIM.perdea.userData.strat='sablare';
function autoStrat(o){
if(o.userData&&o.userData.strat) return o.userData.strat;
if(o.isSprite) return 'etichete';
if(o.isLight||o===grupProfil) return null;
if(o.isLine&&!o.userData.strat) return 'hala';
var L=o.position.x+HALA_L/2, W=HALA_W/2-o.position.z, y=o.position.y;
if(L<0.6) return 'hala';
if(y>7.2) return 'poduri';
var g=o.geometry&&o.geometry.parameters;
if(g&&g.height===H_PERETE) return 'pereti';
if(W<22) return (L<32)?'sablare':'gaurire';
return (L>=45)?'sudura':'camere';
}
grup.children.slice().forEach(function(o){
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
scena.children.slice().forEach(function(o){
if(o===grup||o===grupProfil||o.isLight) return;
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
document.querySelectorAll('#straturi input').forEach(function(cb){
cb.addEventListener('change',function(){
var st=cb.getAttribute('data-strat');
STRAT_ON[st]=cb.checked;
STRATURI[st].forEach(function(o){o.visible=cb.checked;});
});
});
})();
/* ============ COTARE VIE + DETECTIE COLIZIUNI ============ */
var COT_LIMITA=1.2; /* culoar minim admis intre echipamente, m */
var conturDrag=null, cotaSprite=[], conturCol=null;
function faceContur(culoare){
var g=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3(),
new THREE.Vector3(),new THREE.Vector3(),new THREE.Vector3()]);
var l=new THREE.Line(g,new THREE.LineBasicMaterial({color:culoare,linewidth:2}));
l.visible=false; scena.add(l); return l;
}
conturDrag=faceContur(0x2ECC71); conturCol=faceContur(0xE74C3C);
function spriteText(){
var c=document.createElement('canvas'); c.width=256; c.height=64;
var t=new THREE.CanvasTexture(c);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false}));
sp.scale.set(7,1.75,1); sp.visible=false; scena.add(sp);
sp.userData={cv:c,tx:t};
return sp;
}
for(var ci=0;ci<4;ci++) cotaSprite.push(spriteText());
function scrieSprite(sp,txt,culoare){
var c=sp.userData.cv, g=c.getContext('2d');
g.clearRect(0,0,256,64);
g.font='600 30px Segoe UI, sans-serif'; g.textAlign='center';
var w=g.measureText(txt).width+26;
g.fillStyle='rgba(16,12,18,.92)'; g.fillRect((256-w)/2,10,w,44);
g.fillStyle=culoare; g.fillText(txt,128,42);
sp.userData.tx.needsUpdate=true; sp.visible=true;
}
function limiteUnitate(u){
return {L1:u.b[0]+u.off.x, L2:u.b[1]+u.off.x, W1:u.b[2]-u.off.z, W2:u.b[3]-u.off.z};
}
function verificaColiziuni(u){
var a=limiteUnitate(u), rez={ok:true,cu:null};
if(a.L1<0.2||a.L2>74.6||a.W1<0.2||a.W2>42.6){rez.ok=false;rez.cu='peretele halei';return rez;}
for(var i=0;i<UNITATI.length;i++){
if(UNITATI[i]===u) continue;
var b=limiteUnitate(UNITATI[i]);
var supraL=Math.min(a.L2,b.L2)-Math.max(a.L1,b.L1);
var supraW=Math.min(a.W2,b.W2)-Math.max(a.W1,b.W1);
if(supraL>-COT_LIMITA && supraW>-COT_LIMITA){rez.ok=false;rez.cu=UNITATI[i].nume;return rez;}
}
return rez;
}
function aratacCote(u){
var a=limiteUnitate(u), c=verificaColiziuni(u);
var linie=c.ok?conturDrag:conturCol, alta=c.ok?conturCol:conturDrag;
alta.visible=false;
var y=0.06;
var p=[poz(a.L1,a.W1,y),poz(a.L2,a.W1,y),poz(a.L2,a.W2,y),poz(a.L1,a.W2,y)];
p.push(p[0].clone());
linie.geometry.setFromPoints(p); linie.visible=true;
var col=c.ok?'#EAE4DC':'#FF8A7A';
scrieSprite(cotaSprite[0],'la perete sud '+a.L1.toFixed(1)+' m',col);
cotaSprite[0].position.copy(poz(a.L1/2,(a.W1+a.W2)/2,1.4));
scrieSprite(cotaSprite[1],'la perete nord '+(74.8-a.L2).toFixed(1)+' m',col);
cotaSprite[1].position.copy(poz((a.L2+74.8)/2,(a.W1+a.W2)/2,1.4));
scrieSprite(cotaSprite[2],'la perete est '+a.W1.toFixed(1)+' m',col);
cotaSprite[2].position.copy(poz((a.L1+a.L2)/2,a.W1/2,1.4));
scrieSprite(cotaSprite[3],'la perete vest '+(42.8-a.W2).toFixed(1)+' m',col);
cotaSprite[3].position.copy(poz((a.L1+a.L2)/2,(a.W2+42.8)/2,1.4));
toast(c.ok? ('Muti: '+u.nume) : ('SUPRAPUNERE cu '+c.cu));
}
function ascundeCote(){
conturDrag.visible=false; conturCol.visible=false;
cotaSprite.forEach(function(sp){sp.visible=false;});
}
/* ============ TRASEE DE UTILITATI ============ */
function tub(p1,p2,r,culoare,strat){
var d=new THREE.Vector3().subVectors(p2,p1), len=d.length();
var m=new THREE.Mesh(new THREE.CylinderGeometry(r,r,len,8),
new THREE.MeshStandardMaterial({color:culoare,roughness:.55,metalness:.2,
emissive:new THREE.Color(culoare),emissiveIntensity:.3}));
m.position.copy(p1).addScaledVector(d,.5);
m.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.clone().normalize());
m.userData.strat=strat; m.visible=false; scena.add(m);
STRATURI[strat].push(m); return m;
}
STRATURI.aer=[]; STRATURI.electric=[]; STRATURI.aspiratie=[];
STRAT_ON.aer=false; STRAT_ON.electric=false; STRAT_ON.aspiratie=false;
/* aer comprimat - albastru, pe peretele est, coborari la camera de sablare si la posturi */
(function(){
var y=6.2;
tub(poz(2,1.2,y),poz(72,1.2,y),.13,0x2E86DE,'aer');
[16,48,68].forEach(function(L){ tub(poz(L,1.2,y),poz(L,1.2,1.6),.10,0x2E86DE,'aer'); });
tub(poz(29,1.2,y),poz(29,26.3,y),.12,0x2E86DE,'aer');
tub(poz(29,26.3,y),poz(29,26.3,2.2),.10,0x2E86DE,'aer');
})();
/* alimentare electrica - galben, jgheab principal + coborari la tablouri */
(function(){
var y=7.0;
tub(poz(4,21.5,y),poz(72,21.5,y),.16,0xF1C40F,'electric');
[[16,4],[48,4],[29,26.3],[29,32.8],[60,31.8]].forEach(function(p){
tub(poz(p[0],21.5,y),poz(p[0],p[1],y),.11,0xF1C40F,'electric');
tub(poz(p[0],p[1],y),poz(p[0],p[1],2.0),.09,0xF1C40F,'electric');
});
})();
/* aspiratie - gri, de la zonele de sudura si camere spre exterior */
(function(){
var y=5.6;
tub(poz(52,40.5,y),poz(70,40.5,y),.3,0x95A5A6,'aspiratie');
[52.5,60.5,68.5].forEach(function(L){ tub(poz(L,40.5,y),poz(L,34,y),.22,0x95A5A6,'aspiratie'); });
tub(poz(29,26.3,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,32.8,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,22,5.6),poz(70,22,5.6),.3,0x95A5A6,'aspiratie');
})();
/* ================= MUTARE ECHIPAMENTE (drag & drop) ================= */
var MUTARE=false, dragUnit=null;
var UNITATI=[
{id:'tunel', nume:'Linia de sablare', b:[0.3,32.4, 0.6,11.2]},
{id:'rastel', nume:'Spațiu de depozitare material brut', b:[0.3,12.7, 9.6,16.0]},
{id:'gaurire',nume:'Linia de găurire + debitare',b:[31.6,73.8, 0.6,9.0]},
{id:'sud1', nume:'Zona sudură 1', b:[64.4,72.6,22.2,42.6]},
{id:'sud2', nume:'Zona sudură 2', b:[56.4,64.4,22.2,42.6]},
{id:'sud3', nume:'Zona sudură 3', b:[48.4,56.4,22.2,42.6]},
{id:'camsab', nume:'Camera de sablare', b:[21.7,38.0,23.2,29.6]},
{id:'camvops',nume:'Camera de vopsire', b:[21.7,38.0,29.7,36.2]},
{id:'depozit',nume:'Depozitare produs finit', b:[3.0,19.5,26.6,41.6]}
];
UNITATI.forEach(function(u){u.membri=[]; u.mesh=[]; u.off={x:0,z:0};});
var EXCLUSE=[pist1,pist2,jetSab,jetVops,furtunSab,carucior,traversa].concat(cabluri);
function inUnitate(o){
if(EXCLUSE.indexOf(o)>=0) return null;
var st=o.userData&&o.userData.strat;
if(st==='hala'||st==='poduri') return null;
var L=o.position.x+HALA_L/2, W=HALA_W/2-o.position.z;
for(var i=0;i<UNITATI.length;i++){
var b=UNITATI[i].b;
if(L>=b[0]&&L<=b[1]&&W>=b[2]&&W<=b[3]) return UNITATI[i];
}
return null;
}
function adaugaLaUnitate(o){
var u=inUnitate(o); if(!u)return;
o.userData.pozInit=o.position.clone();
u.membri.push(o);
o.traverse(function(c){if(c.isMesh||c.isSprite){c.userData.unit=u; u.mesh.push(c);}});
}
grup.children.slice().forEach(adaugaLaUnitate);
scena.children.slice().forEach(function(o){
if(o===grup||o===grupProfil||o.isLight)return;
adaugaLaUnitate(o);
});
var ray=new THREE.Raycaster(), ndc=new THREE.Vector2(), planSol=new THREE.Plane(new THREE.Vector3(0,1,0),0);
function razaDinEvent(e){
var r=rend.domElement.getBoundingClientRect();
ndc.set(((e.clientX-r.left)/r.width)*2-1, -((e.clientY-r.top)/r.height)*2+1);
ray.setFromCamera(ndc,camera);
}
function toast(txt){
var t=document.getElementById('toast');
if(!txt){t.style.display='none';return;}
t.textContent=txt; t.style.display='block';
}
function alegeUnitate(e){
razaDinEvent(e);
var tinte=[]; UNITATI.forEach(function(u){tinte=tinte.concat(u.mesh);});
var hit=ray.intersectObjects(tinte,false)[0];
if(!hit)return null;
var u=hit.object.userData.unit;
var p=new THREE.Vector3(); ray.ray.intersectPlane(planSol,p);
aratacCote(u);
return {u:u, ultim:p.clone()};
}
function mutaUnitate(e){
razaDinEvent(e);
var p=new THREE.Vector3();
if(!ray.ray.intersectPlane(planSol,p))return;
var d=dragUnit, u=d.u;
var dx=p.x-d.ultim.x, dz=p.z-d.ultim.z; d.ultim.copy(p);
var nx=u.off.x+dx, nz=u.off.z+dz;
/* limiteaza in interiorul halei: L-offset = dx, W-offset = -dz */
nx=Math.max(0.3-u.b[0], Math.min(74.5-u.b[1], nx));
var nw=-nz; nw=Math.max(0.3-u.b[2], Math.min(42.5-u.b[3], nw)); nz=-nw;
var ax=nx-u.off.x, az=nz-u.off.z;
u.off.x=nx; u.off.z=nz;
u.membri.forEach(function(o){o.position.x+=ax; o.position.z+=az;});
aratacCote(u);
}
document.getElementById('btn-muta').onclick=function(){
MUTARE=!MUTARE;
this.classList.toggle('activ',MUTARE);
['btn-resetpoz','btn-salveaza','btn-incarca'].forEach(function(id){
document.getElementById(id).style.display=MUTARE?'':'none';});
if(MUTARE){ruleaza=false; btnPlay.textContent='Pornire';
toast('Trage de un echipament ca sa il muti. Fundalul roteste camera.');
setTimeout(function(){if(!dragUnit)toast(false);},3500);
} else toast(false);
};
document.getElementById('btn-resetpoz').onclick=function(){
UNITATI.forEach(function(u){
u.membri.forEach(function(o){if(o.userData.pozInit)o.position.copy(o.userData.pozInit);});
u.off.x=0; u.off.z=0;
});
};
/* --- salvare / incarcare layout in fisier JSON --- */
document.getElementById('btn-salveaza').onclick=function(){
var date={proiect:'linie',data:new Date().toISOString().slice(0,16),
pozitii:UNITATI.map(function(u){return {id:u.id,x:+u.off.x.toFixed(3),z:+u.off.z.toFixed(3)};})};
var blob=new Blob([JSON.stringify(date,null,1)],{type:'application/json'});
var a=document.createElement('a');
a.href=URL.createObjectURL(blob);
a.download='layout_linie_'+date.data.replace(/[:T]/g,'-')+'.json';
a.click();
toast('Layout salvat in Downloads'); setTimeout(function(){toast(false);},2500);
};
document.getElementById('btn-incarca').onclick=function(){document.getElementById('fis-layout').click();};
document.getElementById('fis-layout').addEventListener('change',function(ev){
var f=ev.target.files[0]; if(!f)return;
var r=new FileReader();
r.onload=function(){
try{
var date=JSON.parse(r.result);
/* intai reset la initial */
UNITATI.forEach(function(u){
u.membri.forEach(function(o){if(o.userData.pozInit)o.position.copy(o.userData.pozInit);});
u.off.x=0; u.off.z=0;
});
(date.pozitii||[]).forEach(function(p){
var u=UNITATI.filter(function(x){return x.id===p.id;})[0]; if(!u)return;
u.off.x=p.x||0; u.off.z=p.z||0;
u.membri.forEach(function(o){o.position.x+=u.off.x; o.position.z+=u.off.z;});
});
toast('Layout incarcat: '+(date.data||f.name)); setTimeout(function(){toast(false);},2500);
}catch(e){toast('Fisier layout invalid'); setTimeout(function(){toast(false);},2500);}
ev.target.value='';
};
r.readAsText(f);
});
addEventListener('resize',function(){
camera.aspect=innerWidth/innerHeight; adapteazaCamera();
rend.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>
