Configurație de referință
Linie completă cu laser pentru profile și tablă
De la profilul brut de 12 m la structura vopsită, într-un singur flux. O mașină cu laser fiber de 20 kW înlocuiește găurirea mecanică și ferăstrăul și taie și tabla. Configurație de referință, dimensionată de la 100 de tone pe lună.
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 schimbă laserul
Varianta aceasta pleacă de la aceeași hală și același tonaj ca linia cu găurire CNC și ferăstrău, dar schimbă inima liniei: o singură mașină cu laser fiber taie profilele pe toate fețele, face șanfrenul și debitează și tabla pentru gusee, plăci de bază și rigidizări. Se adaugă și o etapă nouă, sablarea locală a cordoanelor de sudură, înainte de vopsire, cu o instalație cu vid, fără praf și fără cabină.
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ă. Profilele mari trec culcate.
- 6 turbine × 11 kW, abraziv alice și grit de oțel în circuit închis
- Fereastră utilă 2.000 × 600 mm; profile și tablă, piesă până la 2.000 × 500 × 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


2Debitare laser pentru profile și tablă
O singură mașină, în continuarea tunelului, înlocuiește găurirea mecanică și ferăstrăul cu bandă. Găurile, decupajele, marcajele, șanfrenul și tăierea la lungime se fac dintr-o singură prindere, direct din modelul 3D.
- Laser fiber de 20 kW, cap de tăiere cu înclinare (3 în 1: profile, tablă, șanfren)
- Profile H, I, U, L de la 200 × 100 până la 1.500 × 500 mm, lungime până la 12,5 m; acoperă HEB / HEM 1000
- Tablă pe două stații de 13 × 3,5 m (12 × 3 m la șanfren), cu aceeași sursă
- Șanfren la 90° și ±45°, precizie de poziționare ±0,1 mm/m, repetabilitate ±0,1 mm
- Profilul se scanează la încărcare, iar programul se corectează automat după forma reală; anticoliziune
- Nesting pentru profile și tablă; import DXF, DWG și modele 3D, compatibil Tekla
- Circa 36 × 6 m, în continuarea tunelului; putere circa 110 kW

3–6Două zone de asamblare și sudură
Fiecare zonă are două posturi. Sudura rămâne operația care dă ritmul liniei: 90 de minute din cele 198 ale ciclului. Zona 1 lucrează pe rotatoare, zona 2 pe o masă de sudură de 6 m, iar zona 3 e rezervată.
- Patru instalații MIG/MAG cu puls de 400 A, câte două pe zonă
- Zona 1: rotatoare cu lanț pentru profile de 12 m, fără pod rulant
- Zona 2: masă modulară de 6 × 1,5 m, din patru module cu blat de 25 mm și rețea de găuri de 28 mm
- Filtrare a fumului: 2 unități compacte × 12.000 m³/h, cu recirculare în hală (circa 4,6 schimburi/h); se extinde la 3 sau 5 unități
- Dotare standard: detecția scânteilor și convertizor de frecvență



7–9Sablarea cordoanelor și incinta de vopsire
După asamblare, cordoanele de sudură se sablează local, în zona 3, cu o instalație cu vid, în circuit închis, fără praf și fără cabină. Apoi subansamblul trece în incinta de vopsire, unde vopseaua se aplică cu o pompă airless.
- Sablare locală cu vid la Sa 2½ (ISO 8501-1), 6 – 15 m²/h, abraziv alice de oțel 0,8 – 1,0 mm; praful se colectează în saci
- Incintă de 12,8 × 3,9 × 3,9 m, poartă motorizată de 3,7 × 3,7 m și două uși de serviciu
- Flux longitudinal de aer de 0,5 m/s, circa 30.000 m³/h; uscare forțată la 40 – 60 °C
- Încălzire electrică de 330 kW în 6 trepte, cu recuperator rotativ de căldură (randament peste 50%), fără branșament de gaz
- Pompă airless pneumatică pentru vopsele anticorozive cu conținut mare de solide; două cărucioare tehnologice de 3 t pentru piese
Aceasta e o configurație de referință: parametrii se confirmă pe proiectul fiecărui client, înainte de ofertă.
Pas cu pas: ce se întâmplă cu un profil de 12 m
Cele 12 etape de mai jos urmează ciclul din dosarul tehnic. Duratele sunt calculate pe un avans de 1 m/min în tunel. În simularea 3D, debitarea laser apare în două etape (găuri și decupaje, apoi șanfren).
Operația critică e sudura: 90 de minute din 198, adică 45% din ciclu. De aceea sunt două zone de asamblare, câte două posturi fiecare, și o a treia zonă rezervată. 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ă posturi și zone de sudură, nu un al doilea tunel.
Cum se așază în hală
Fluxul se închide în formă de U. Tunelul și mașina laser stau în linie, pe peretele lung, și lasă liber un culoar central de 11,5 m în dreptul tunelului și de circa 8 m în dreptul mașinii laser.

- Tunelul de sablare cere 31,5 m liniari continui; deschiderile de 21 m, fără stâlpi intermediari, îndeplinesc condiția
- Mașina laser cere 36 m liniari și 6 m lățime, în continuarea tunelului: linia inseriată are 67,5 m, într-o hală de 74,8 m
- În fața porții incintei de vopsire 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 incintei de vopsire ș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 770 kW, din care 330 kW bateria electrică a incintei de vopsire; recomandăm un post de transformare de 1.000 kVA. Procedura de racord este termenul cel mai lung din proiect, așa că se pornește din timp.
- Aer comprimat: circa 7,5 m³/min la 8 bar (sablarea cu vid a cordoanelor circa 6,1 m³/min, pompa airless circa 1,5 m³/min; sablarea și vopsirea nu lucrează simultan), cu uscător frigorific, rezervor tampon și rețea de distribuție. Compresorul (circa 45 kW) poate fi cumpărat sau închiriat, dar trebuie să fie disponibil la punerea în funcțiune.
- Gaze pentru laser: oxigen sau azot (butelii sau rezervor); aerul comprimat uscat servește la acționări și, opțional, ca gaz de tăiere.
- Pod rulant: pentru profilele de 12 m și pentru subansamble, pe toată lungimea halei, inclusiv pentru alimentarea celor două stații ale mașinii laser.
- Lucrări de construcții: platformă plană din beton de 36 × 6 m pentru mașina laser, 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 echipamentele liniei, manipularea dintre ele (spațiu de depozitare, transferuri transversale, mese de role), transportul, montajul, punerea în funcțiune, instruirea (4 zile pentru mașina laser: programare, operare, întreținere) ș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. Mașina laser se livrează în minimum 150 de zile de la comanda fermă, corelat cu stadiul halei.
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ă cu laser - 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ă cu laser pentru profile și tablă</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>Debitare laser<br>
<span class="cub" style="background:#D8C271"></span>Zone de sudură<br>
<span class="cub" style="background:#A98BC0"></span>Camera de 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>Debitare laser</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 18 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(60.5,68,AXA); /* v3: laser 33-60 m, role de descarcare 60,5-68 m */ /* masa de intrare gaurire 8 m (rev. 14.09) */
/* ---------- 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);
})();
/* ---------- MASINA DE DEBITARE LASER (profile + tabla, doua statii) ---------- */
(function(){
var alb=mat(0xE8ECEF,{r:.55}), albastru=mat(0x2F4F8F,{r:.55}), inchis=mat(0x3A3F46,{r:.55}), verde=mat(0x7BA88A,{r:.55});
// batiu: doua statii de 13 m (33-46 si 47-60), latime 6 m centrata pe AXA
cutie(39.5,AXA,.45,13,5.6,.9,albastru); // statia 1
cutie(53.5,AXA,.45,13,5.6,.9,albastru); // statia 2
// gratare de taiere (lamele) pe fiecare statie
for(var i=0;i<26;i++){ cutie(33.4+i*.5,AXA,.95,.08,5.2,.18,inchis); cutie(47.4+i*.5,AXA,.95,.08,5.2,.18,inchis); }
// sine longitudinale ale portalului, 33-60 m
cutie(46.5,AXA-3.1,.5,27,.3,.35,inchis); cutie(46.5,AXA+3.1,.5,27,.3,.35,inchis);
// protectii laterale din tabla (pereti scunzi) pe toata lungimea
cutie(46.5,AXA-3.25,1.35,27,.1,1.5,alb,false); cutie(46.5,AXA+3.25,1.35,27,.1,1.5,alb,false);
// portal (gantry) la 48.5 m: doua coloane + traversa, cap laser
cutie(48.5,AXA-2.9,1.7,1.4,.6,3.4,alb);
cutie(48.5,AXA+2.9,1.7,1.4,.6,3.4,alb);
cutie(48.5,AXA,3.55,1.2,6.4,.7,alb); // traversa
cutie(48.5,AXA,3.0,.9,.9,.6,inchis); // carucior Y
var axV=new THREE.Mesh(new THREE.CylinderGeometry(.14,.14,1.2,12),mat(0x2F4F8F,{m:.5}));
axV.position.copy(poz(48.5,AXA,2.2)); grup.add(axV); // cap laser (se misca pe verticala)
var axH1=new THREE.Mesh(new THREE.CylinderGeometry(.01,.01,.01,4),inchis); axH1.position.copy(poz(48.5,AXA,1.2)); grup.add(axH1);
var axH2=axH1.clone(); 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;
// sursa laser + chiller + dulap comanda, pe latura de vest a masinii
cutie(44,8.2,.9,2.2,1.0,1.8,mat(0x5E6670)); // sursa laser 20 kW
cutie(46.6,8.2,.8,1.6,1.0,1.6,mat(0x5E6670)); // chiller
cutie(50,8.2,1.0,1.4,1.0,2.0,mat(0x5E6670)); // dulap + consola operator
cutie(62,8.0,1.2,.8,.8,2.4,mat(0x39424E)); // exhaustare
etich('DEBITARE LASER',46.5,AXA,7.2,12);
})();
/* brat de alimentare (carucior cu clema) - pozitionarea profilului pe statie */
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.5,6.5,.35,27,.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 (v3: zona 1 rotatoare, zona 2 masa 6 m, zona 3 ulterior) ---------- */
(function(){
var galben=mat(0xD8C271,{o:.85}), galbenSlab=mat(0xD8C271,{o:.28});
var gri=mat(0x50565E,{m:.3}), inchis=mat(0x3A3F46), rosu=mat(0x8A2F2F), otel=mat(0x5E7889);
function aparat(L,W){ // sursa MIG/MAG pe carucior + butelie
cutie(L,W,.55,.7,.5,1.0,rosu);
var but=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,1.1,10),otel);
but.position.copy(poz(L+.5,W-.3,.6)); grup.add(but);
}
// zona 1 (65-72 m): doua posturi pe rotatoare cu lant
cutie(68.5,31.8,.12,7,18,.24,galben,false);
[66.3,70.7].forEach(function(L){
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);
});
aparat(65.9,28.6); aparat(71.1,35.0);
etich('SUDURĂ 1 - ROTATOARE',68.5,31.8,4.6,9);
// zona 2 (57-64 m): doua posturi pe masa de sudura modulara 6 x 1,5 m
cutie(60.5,31.8,.12,7,18,.24,galben,false);
cutie(60.5,31.8,.9,6,1.5,.14,gri); // blat 25 mm, 4 module
for(var m=1;m<4;m++) cutie(57.5+m*1.5,31.8,.98,.03,1.5,.02,inchis); // rosturile modulelor
[[-2.7,-.55],[-2.7,.55],[-.9,-.55],[-.9,.55],[.9,-.55],[.9,.55],[2.7,-.55],[2.7,.55]].forEach(function(p){
cutie(60.5+p[0],31.8+p[1],.42,.12,.12,.84,inchis);
});
aparat(57.9,28.6); aparat(63.1,35.0);
etich('SUDURĂ 2 - MASĂ 6 m',60.5,31.8,4.6,9);
// zona 3 (49-56 m): rezervata, se echipeaza ulterior
cutie(52.5,31.8,.12,7,18,.24,galbenSlab,false);
etich('ZONA 3 - SABLARE CORDOANE',52.5,31.8,4.6,9);
cutie(50.2,26.8,.08,.9,.9,.12,mat(0x3A3F46));
cutie(50.2,26.8,.85,1.8,.8,1.7,mat(0x2F4F8F,{m:.3})); /* unitate de sablare cu vid AT200 */
cutie(50.2,27.6,1.9,.5,.5,.5,mat(0x8A939E)); /* siloz / separator */
// doua unitati compacte de filtrare (varianta A), cate una pe zona activa
[68.5,60.5].forEach(function(L){
cutie(L,41.6,1.4,1.6,1.6,2.8,mat(0x2F4F8F,{r:.5})); // corpul inferior (filtru)
cutie(L,41.6,3.5,1.5,1.5,1.3,mat(0xE8ECEF,{r:.55})); // corpul superior cu duze
for(var k=0;k<3;k++){ var dz=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,.5,10),mat(0x2F4F8F)); dz.rotation.x=Math.PI/2; dz.position.copy(poz(L-.5+k*.5,40.7,3.7)); grup.add(dz); }
});
etich('FILTRE FUM 2 x 12.000',64.5,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,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]].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 activa (1 si 2) */
var sudori=[];
[[68.5],[60.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); opCamSab.g.scale.set(0.001,0.001,0.001);
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 mașina laser',d:'Profilul curățat trece pe stația 1 a mașinii laser, unde este scanat și poziționat.',
t:5,c:[[26,AXA,HL,'tunel'],[40,AXA,HL,'gaurire']]},
{n:'Debitare laser',d:'Capul laser taie găurile, decupajele și marcajele pe toate fețele profilului, dintr-o singură prindere.',
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:'Șanfren și tăiere la lungime',d:'Capul înclinat la 45 de grade taie șanfrenul și lungimea finală, 11,95 m.',
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:'Descărcare',d:'Profilul debitat trece pe rolele de descărcare, la capătul mașinii.',
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:'Zona 1: piesa pe rotatoare, două posturi MIG/MAG în puls; rotatoarele întorc piesa fără pod. Zona 2 lucrează pe masa de 6 m; zona 3 se echipează ulterior.',
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:'Sablarea locală a cordoanelor',d:'Podul rulant duce subansamblul în zona 3, unde cordoanele și zonele adiacente se sablează local la Sa 2½ cu instalația de sablare cu vid, în circuit închis, fără praf.',
t:9,c:[[68.5,31.8,HS,'sud1'],[52.5,31.8,HS,'sud3']],arc:2.0,pod:'V',
lum:function(t){var o=OFU('sud3');
return t>.45?{p:poz(52.5+o.L+(Math.random()-.5)*3,31.8+o.W,1.3),c:0xFFC86B,i:2.0+Math.random()*1.2}:null;}},
{n:'Transport spre camera de vopsire',d:'Podul rulant din vest duce subansamblul prin culoarul de manevră, la intrarea camerei de vopsire.',
t:8,c:[[52.5,31.8,HS,'sud3'],[42,32.8,HS,'camvops']],arc:2.4,pod:'V'},
{n:'Camera de vopsire',d:'Flux longitudinal de aer, încălzire electrică cu recuperator de căldură, uscare forțată. Ieșirea se face prin capătul opus.',
t:10,c:[[42,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,8,4,6,5,90,15,6,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<9) matCordon.color.copy(CUL.cordon);
else if(idx===9){
var kc=Math.min(Math.max((t-.55)/.4,0),1);
matCordon.color.copy(CUL.cordon.clone().lerp(CUL.sa3,kc));
} else if(idx===10){
matCordon.color.copy(CUL.sa3);
} else if(idx===11){
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=false;
jetSab.visible=sabActiv&&STRAT_ON.operatori; furtunSab.visible=false;
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:'Mașina de debitare laser pentru profile și tablă'},
{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 18 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+' bare in paralel prin tunel. Fereastra de 1.700 x 1.200 mm le preia simultan; capacitatea de sablare se multiplica, iar masina laser preia apoi cate o bara.');
} 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 laser, 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:'Debitare laser',min:12},
{n:'Sudură', min:90},
{n:'Sablare cordoane (vid)', min:15},
{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_laser_flux3D_'+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.5)?'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 laser, tunel, posturi si camera de vopsire */
(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,30.6,y),.12,0x2E86DE,'aer');
tub(poz(29,30.6,y),poz(29,30.6,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,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:'Mașina de debitare laser',b:[32.5,68.5, 0.6,9.6]},
{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 3 - sablare cordoane', b:[48.4,56.4,22.2,42.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>
