Reference configuration
Complete line for steel structures
From a 12 m raw profile to the painted structure in a single flow. A reference configuration sized from 100 tonnes a month: blasting, CNC drilling, bevel cutting, welding, blasting of the welded assemblies and painting.
Interactive 3D simulation
The same line in 3D: rotate the model, follow the profile from entry to exit and see which machine works at each stage. You can start the “Cinematic tour”, switch off layers you do not need, switch between profile and plate and see the real durations of the stages.
What this line is
A steel structure fabricator loses time and money in the same places: profiles are blasted by third parties, marked out and drilled by hand, and the assemblies reach the paint shop after they have started to rust again. The line below closes the whole flow inside the shop, in a U shape, with one line PLC and one acceptance test.
It is a reference configuration, calculated on a real specification: hot-rolled profiles from IPE 80 up to HEB and HEM 1000, 12 m profiles of up to 4.19 t each, cleaned to SA 2.5 according to ISO 8501-1. We do not deliver it identical every time: we adapt it to each customer’s profiles, tonnage and building.
The seven modules
Each module has its own job in the flow, and together they remove the handling and the round-trip transport to third parties.

1Roller-conveyor blasting tunnel
It cleans the raw profile on all faces in a single pass, before cutting and welding, while the part geometry is still simple.
- 6 turbines × 11 kW, steel shot and grit abrasive in a closed circuit
- Working window 1,700 × 1,200 mm; profiles and plate, parts up to 1,500 × 1,000 × 12,000 mm
- Cleaning speed 0.5 – 1.5 m/min, adjustable
- Abrasive separation above 99.5%; filtration with emissions of at most 5 mg/m³
- 12 m roller tables at the entry and at the exit


2CNC drilling and bevel cutting
One vertical spindle for the web and two horizontal ones for the flanges drill at the same time. A tandem band saw cuts at an angle, to make the bevel. The line is Akyapak. You will find the full range in our section on profile cutting and processing lines.
- 3 spindles with automatic tool changers, holes Ø 8 – 50 mm
- Profiles from 200 × 80 mm up to 1,200 × 500 mm
- Tapping M10 – M24, marking, automatic measurement of the material
- CAM software with 3D model import, no marking out
- 12 m conveyors and cross transfers

3–5Three assembly and welding zones
Each zone has two operators. Welding sets the pace of the whole line: 90 minutes of the 213-minute cycle.
- Four pulsed MIG/MAG machines of 400 A: three active and one spare
- 18 × 7 m zones, with 10 or 15 m interconnecting packages
- Chain rotators to turn the assemblies, with no crane
- Weld fume filtration: 2 units × 16,000 m³/h, recirculating into the shop
- Standard: spark detection and a frequency converter



6–7Blasting chamber and paint booth
The welded assembly enters the blasting chamber for the welds and cut edges, then goes straight into the paint booth. There are doors at both ends, so the part never turns back.
- Two spaces of 13 × 4 × 4 m, with access at both ends
- Blasting chamber: SA 2.5, mechanical recovery over the whole floor, emissions below 1 mg/m³
- Painting with longitudinal airflow, about 18,000 m³/h, against over 50,000 m³/h with vertical flow
- Forced drying, explosion-proof lighting, CE and ATEX compliance
In this configuration the blasting chamber and the paint booth are a preliminary specification: the parameters are confirmed with the manufacturer before a quotation.
Step by step: what happens to a 12 m profile
The 13 stages below are the ones in the 3D simulation. Durations are calculated on a feed rate of 1 m/min, both in the tunnel and on the drill line.
The critical operation is welding: 90 minutes of 213, or 42% of the cycle. That is why there are three assembly zones with two operators each. The rest of the line has spare capacity: the blasting tunnel is occupied between 0.4 and 4.5 hours a day, even when it processes only light profiles. If volume grows, you add welding zones, not a second tunnel.
How it is laid out in the building
The flow closes in a U shape. The blasting and drilling lines run along the long wall and leave a free central aisle of 11.5 m along the whole length of the building, for the overhead cranes and the forklift.

- The blasting tunnel needs 31.5 m of continuous length; the 21 m bays, with no intermediate columns, meet the condition
- The drill line with its tables needs 41.5 m of length
- A free aisle of 13 m must remain in front of the chamber doors, for 12 m parts
- The clear height under the beam must allow for the 4 m height of the chambers and the filtration units
What a building needs for this kind of installation is described in our article what you need before you install a blasting machine.
What the building has to provide
The calculation also produces the list of items that fall to the customer. Some of them take longer than building the machines:
- Electrical supply: estimated installed power of about 450 kW; we recommend a 630 kVA transformer station. The grid connection procedure is the longest lead time in the project, so it is started early.
- Compressed air: about 13 m³/min at 7 bar for the blasting chamber, with a dryer, a buffer tank and the distribution network.
- Overhead crane: for the 12 m profiles and for the assemblies, along the whole length of the building.
- Civil works: a recess of about 130 mm in the blasting chamber floor, local foundations and cable routes; they are planned at the foundation stage.
- Installation: a forklift or a crane and one or two people during installation.
What is included in the Vibroblast delivery
All seven modules, the handling between them (storage area, cross transfers, roller tables), line automation under a single PLC with a central console, transport, installation, commissioning, training and documentation. Acceptance is carried out on the whole line, with a production test on the customer’s own profiles and a check of the SA 2.5 cleanliness grade. The indicative delivery time is 5 – 6 months from a firm order.
Tell us which profiles you process
Send us the type and maximum size of the profiles, how many tonnes a month and the building plan. From those we propose a configuration and tell you what it needs in power, compressed air and floor space. You do not have to take everything at once: most customers start with the stage that hurts most, usually blasting.
Ask for a quotation for a complete lineAsk for the free audit
Read also
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Complete line for steel structures - 3D flow</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 · Complete line for steel structures</h1>
<div class="sub">Complete production line — 42.8 × 74.8 m building, U-shaped flow. Rotate with the mouse, zoom with the scroll wheel, pan with right click.</div>
</div>
<div class="panou" id="etapa">
<div class="nr">STAGE <span id="et-nr">1</span> / 13</div>
<div class="nume" id="et-nume">Raw material storage area</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">Play</button>
<input type="range" id="scrub" min="0" max="1000" value="0">
<span class="eticheta">Speed</span>
<input type="range" id="viteza" min="20" max="300" value="100">
<button class="secundar" id="btn-urmarire">Follow the profile</button>
<button class="secundar" id="btn-camera">Overview</button>
<button class="secundar" id="btn-foto">Capture</button>
<span class="eticheta">Light</span>
<input type="range" id="lumina" min="40" max="180" value="100" style="width:80px">
<button class="secundar" id="btn-straturi">Layers</button>
<button id="btn-tur" style="background:#E0A63C;color:#3E1122">Cinematic tour</button>
<button class="secundar" id="btn-paralel">Blasting: 1 profile</button>
<button class="secundar" id="btn-tabla">Plate</button>
<button class="secundar" id="btn-timp">Real time</button>
<button class="secundar" id="btn-multi">Continuous flow</button>
<button class="secundar" id="btn-200">200 t/month test</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>Blasting (tunnel + rollers)<br>
<span class="cub" style="background:#7BA88A"></span>Drilling and cutting<br>
<span class="cub" style="background:#D8C271"></span>Welding zones<br>
<span class="cub" style="background:#A98BC0"></span>Blasting / paint chambers<br>
<span class="cub" style="background:#C9A23F"></span>Overhead cranes
</div>
<div class="panou" id="straturi">
<div class="titluS">Visible layers</div>
<label><input type="checkbox" data-strat="hala" checked>Building (floor, columns, gates)</label>
<label><input type="checkbox" data-strat="pereti" checked>Building walls</label>
<label><input type="checkbox" data-strat="sablare" checked>Blasting line</label>
<label><input type="checkbox" data-strat="gaurire" checked>Drilling + cutting</label>
<label><input type="checkbox" data-strat="sudura" checked>Welding zone</label>
<label><input type="checkbox" data-strat="camere" checked>Chambers + storage</label>
<label><input type="checkbox" data-strat="poduri" checked>Overhead cranes</label>
<label><input type="checkbox" data-strat="operatori" checked>Operators</label>
<label><input type="checkbox" data-strat="etichete" checked>Labels</label>
<div class="titluS" style="margin-top:10px">Utility routes</div>
<label><input type="checkbox" data-strat="aer">Compressed air</label>
<label><input type="checkbox" data-strat="electric">Electrical supply</label>
<label><input type="checkbox" data-strat="aspiratie">Extraction and exhaust</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">
Complete line · 100 t / month</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">
From the raw profile to the painted structure</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">Module load</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">Scenario</span> <b id="contorScenariu" style="float:right">100 t/month</b><br>
<span style="color:#9A8F94">Cycle / assembly</span> <b style="float:right">3 h 33 min</b>
</div>
</div>
<div class="panou" id="toast" style="display:none;top:112px;right:16px;font-size:13px"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
var HALA_L=74.8, HALA_W=42.8, H_PERETE=10, AXA=3.84;
var scena=new THREE.Scene();
scena.background=new THREE.Color(0x151016);
scena.fog=new THREE.Fog(0x151016,130,280);
var camera=new THREE.PerspectiveCamera(48,innerWidth/innerHeight,.1,600);
var rend=new THREE.WebGLRenderer({antialias:true,alpha:false});
rend.setSize(innerWidth,innerHeight);
rend.setPixelRatio(Math.min(devicePixelRatio,2));
rend.shadowMap.enabled=true; rend.shadowMap.type=THREE.PCFSoftShadowMap;
rend.toneMapping=THREE.ACESFilmicToneMapping; rend.toneMappingExposure=0.95;
rend.outputEncoding=THREE.sRGBEncoding;
document.getElementById('scena').appendChild(rend.domElement);
scena.add(new THREE.AmbientLight(0xffffff,.42));
var soare=new THREE.DirectionalLight(0xfff6ea,1.6);
soare.position.set(50,70,40); soare.castShadow=true;
soare.shadow.camera.left=-75; soare.shadow.camera.right=75;
soare.shadow.camera.top=75; soare.shadow.camera.bottom=-75;
soare.shadow.mapSize.set(2048,2048); scena.add(soare);
var umplere=new THREE.DirectionalLight(0x9FB4FF,.3);
umplere.position.set(-40,50,-40); scena.add(umplere);
/* ORIENTARE: X = L (portile la stanga), Z = est spre privitor (linia jos) */
function poz(L,W,y){return new THREE.Vector3(L-HALA_L/2, y||0, HALA_W/2-W);}
var grup=new THREE.Group(); scena.add(grup);
var ANIM={oameni:[]};
var STRAT_ON={hala:true,pereti:true,sablare:true,gaurire:true,sudura:true,camere:true,poduri:true,operatori:true,etichete:true};
function mat(c,opt){var o=opt||{};
var m=new THREE.MeshStandardMaterial({color:c,roughness:o.r!==undefined?o.r:.6,
metalness:o.m||0,transparent:o.o!==undefined,opacity:o.o!==undefined?o.o:1});
/* utilajele primesc o emisie proprie discreta ca sa nu se piarda pe fundal inchis */
if(!(o.r>=.9)){ m.emissive=new THREE.Color(c); m.emissiveIntensity=0.22; }
return m;}
function cutie(L,W,y,dl,dw,h,material,umbre){
var ms=new THREE.Mesh(new THREE.BoxGeometry(dl,h,dw),material);
ms.position.copy(poz(L,W,y)); if(umbre!==false){ms.castShadow=true;ms.receiveShadow=true;}
grup.add(ms); return ms;
}
/* ---------- hala ---------- */
var podea=cutie(HALA_L/2,HALA_W/2,-0.15,HALA_L+0.8,HALA_W+0.8,0.3,mat(0x1C1A24,{r:1.0}));
podea.castShadow=false;
var gmat=new THREE.LineBasicMaterial({color:0x3A3550});
for(var l=0;l<=HALA_L+.1;l+=10.6){
var g=new THREE.BufferGeometry().setFromPoints([poz(l,0,.01),poz(l,HALA_W,.01)]);
grup.add(new THREE.Line(g,gmat));
}
var matPerete=new THREE.MeshStandardMaterial({color:0x6B6480,roughness:.9,
side:THREE.BackSide,transparent:true,opacity:0.16,depthWrite:false});
function zid(L,W,dl,dw,h,y){
var m=cutie(L,W,(y||0)+h/2,dl,dw,h,matPerete,false);
m.userData.strat='pereti';
/* muchie subtire, ca hala sa ramana citibila si cand peretii sunt stinsi */
var ed=new THREE.LineSegments(new THREE.EdgesGeometry(m.geometry),
new THREE.LineBasicMaterial({color:0x8A83A8,transparent:true,opacity:.5}));
ed.position.copy(m.position); ed.userData.strat='pereti'; grup.add(ed);
return m;}
zid(HALA_L/2,0,HALA_L,.3,H_PERETE); // est
zid(HALA_L/2,HALA_W,HALA_L,.3,H_PERETE); // vest
zid(HALA_L,HALA_W/2,.3,HALA_W,H_PERETE); // nord
zid(0,5.475,.3,10.95,H_PERETE); // sud, intre porti
zid(0,21.4,.3,10.9,H_PERETE);
zid(0,37.325,.3,10.95,H_PERETE);
zid(0,13.45,.3,5,H_PERETE-5.5,5.5);
zid(0,29.35,.3,5,H_PERETE-5.5,5.5);
function poarta(W,c){
var m=cutie(0,W,2.75,.35,5,5.5,mat(c,{o:.5}));
m.material.emissive=new THREE.Color(c); m.material.emissiveIntensity=.25;
}
poarta(13.45,0x2E9E5B); poarta(29.35,0x3B6E8F);
for(var l2=.92;l2<HALA_L;l2+=10.6) cutie(l2,21.56,H_PERETE/2,.8,.6,H_PERETE,mat(0x59505F,{r:.7}));
/* ================= ATMOSFERA ================= */
scena.background=new THREE.Color(0x16141E);
scena.fog=null;
/* lumina de umplere calda dinspre podea (bounce) */
var bounce=new THREE.HemisphereLight(0xCFE0FF,0x3B3746,.4); scena.add(bounce);
/* corpuri de iluminat industriale suspendate */
var LAMPI=[];
for(var li=8;li<HALA_L;li+=13){
for(var lw=9;lw<HALA_W;lw+=12){
var cupa=new THREE.Mesh(new THREE.ConeGeometry(.62,.5,14,1,true),
new THREE.MeshStandardMaterial({color:0x4A4350,side:THREE.DoubleSide,roughness:.6}));
cupa.position.copy(poz(li,lw,8.4)); grup.add(cupa);
var bec=new THREE.Mesh(new THREE.SphereGeometry(.2,10,10),
new THREE.MeshBasicMaterial({color:0xFFF3DC}));
bec.position.copy(poz(li,lw,8.15)); grup.add(bec); LAMPI.push(bec);
var tija=new THREE.Mesh(new THREE.CylinderGeometry(.03,.03,1.4,6),mat(0x3A3440));
tija.position.copy(poz(li,lw,9.3)); grup.add(tija);
var pl=new THREE.PointLight(0xFFE9C4,.42,26,2);
pl.position.copy(poz(li,lw,8.0)); scena.add(pl);
}
}
/* raze de lumina prin luminatoarele de acoperis */
var RAZE=[];
for(var ri=12;ri<HALA_L-6;ri+=16){
var raza=new THREE.Mesh(new THREE.CylinderGeometry(2.6,5.2,H_PERETE+2,16,1,true),
new THREE.MeshBasicMaterial({color:0xFFE7BC,transparent:true,opacity:.055,
side:THREE.DoubleSide,depthWrite:false,blending:THREE.AdditiveBlending}));
raza.visible=false;
raza.position.copy(poz(ri,HALA_W*0.42,(H_PERETE+2)/2)); raza.rotation.z=.10;
scena.add(raza); RAZE.push(raza);
}
/* praf in suspensie, prins de lumina */
(function(){
var n=900, g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
for(var i=0;i<n;i++){
var q=poz(Math.random()*HALA_L,Math.random()*HALA_W,.4+Math.random()*7.5);
p[i*3]=q.x; p[i*3+1]=q.y; p[i*3+2]=q.z;
}
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var praf=new THREE.Points(g,new THREE.PointsMaterial({color:0xFFE2B0,size:.075,
transparent:true,opacity:.28,depthWrite:false,blending:THREE.AdditiveBlending}));
scena.add(praf); ANIM.praf=praf; ANIM.prafBase=p.slice();
})();
/* ---------- etichete ---------- */
function etich(txt,L,W,y,scara){
var c=document.createElement('canvas');c.width=512;c.height=110;
var g=c.getContext('2d');
g.font='600 46px Segoe UI, sans-serif';g.textAlign='center';
var w=g.measureText(txt).width+44;
g.fillStyle='rgba(16,12,18,.88)';
g.beginPath();g.rect((512-w)/2,12,w,80);g.fill();
g.fillStyle='#EAE4DC';g.fillText(txt,256,68);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:new THREE.CanvasTexture(c),depthTest:false}));
var s=(scara||11)*((innerWidth<900)?0.78:0.82); sp.scale.set(s,s*110/512,1);
sp.position.copy(poz(L,W,y)); grup.add(sp);
}
/* ---------- role ---------- */
var matRola=mat(0xB0342E,{m:.35,r:.45}), matCadru=mat(0x2E5C8A,{r:.6}), matSina=mat(0xD8C231,{r:.55});
function role(L1,L2,W){
cutie((L1+L2)/2,W,.45,L2-L1,2,.55,matCadru);
cutie((L1+L2)/2,W-0.97,.76,L2-L1,.12,.1,matSina);
cutie((L1+L2)/2,W+0.97,.76,L2-L1,.12,.1,matSina);
for(var l=L1+.4;l<L2;l+=.8){
var r=new THREE.Mesh(new THREE.CylinderGeometry(.11,.11,1.8,10),matRola);
r.rotation.x=Math.PI/2; r.position.copy(poz(l,W,.86)); grup.add(r);
}
for(var l3=L1+2;l3<L2;l3+=4) cutie(l3,W+1.25,.5,.5,.4,.4,matCadru); // motoare
}
role(0.5,12.5,AXA); role(20,32,AXA); role(32,44,AXA); role(61.5,73.5,AXA);
/* ---------- TUNEL DE SABLARE (reconstruit dupa referinta) ---------- */
(function(){
var gri=mat(0x9AA1AA,{r:.6}), inchis=mat(0x3A3F46,{r:.55}), galben=mat(0xE0C231,{r:.55});
/* soclu de beton */
cutie(16.2,AXA,.05,8.6,5.4,.1,mat(0xB5B0A8,{r:.95}),false);
/* vestibule de etansare */
cutie(13.2,AXA,1.65,1.5,3.2,2.9,gri);
cutie(19.1,AXA,1.65,1.5,3.2,2.9,gri);
/* camera de sablare - corp inchis la culoare, ca in referinta */
cutie(16.15,AXA,2.4,4.2,3.7,4.4,inchis);
cutie(16.15,AXA,4.62,4.4,3.9,.14,gri,false);
/* dunga rosie la nivelul rolelor */
cutie(16.15,2.0,0.9,4.2,.05,.12,mat(0xB0342E));
/* 6 turbine cu motoare si discuri rotative (aceleasi pozitii) */
var matTurb=mat(0x565E68,{r:.5}), matMotor=mat(0x3E4650,{m:.3,r:.4});
var pozT=[[15.1,1.35,4.0,.5],[16.25,1.35,3.2,-.4],[17.4,1.35,4.0,.5],
[15.1,6.33,4.0,-.5],[16.25,6.33,3.2,.4],[17.4,6.33,4.0,-.5]];
ANIM.turbine=[];
for(var i=0;i<6;i++){
var p=pozT[i];
var t=new THREE.Mesh(new THREE.BoxGeometry(1.0,1.0,1.0),matTurb);
t.position.copy(poz(p[0],p[1],p[2])); t.rotation.x=p[3]; t.castShadow=true; grup.add(t);
var dirW=(p[1]<AXA)?-1:1;
var mo=new THREE.Mesh(new THREE.CylinderGeometry(.27,.27,.9,12),matMotor);
mo.position.copy(poz(p[0],p[1]+dirW*0.95,p[2])); mo.rotation.x=Math.PI/2; mo.castShadow=true; grup.add(mo);
var disc=new THREE.Mesh(new THREE.CylinderGeometry(.34,.34,.09,16),galben);
disc.rotation.x=Math.PI/2; disc.position.copy(poz(p[0],p[1]+dirW*1.45,p[2])); grup.add(disc);
ANIM.turbine.push(disc);
}
/* buncar si separator DEASUPRA camerei, pe platforma cu balustrada galbena */
var buncar=new THREE.Mesh(new THREE.CylinderGeometry(1.5,.5,1.3,4),gri);
buncar.rotation.y=Math.PI/4; buncar.position.copy(poz(16.15,AXA,5.5)); grup.add(buncar);
cutie(16.15,AXA,6.7,2.8,1.9,1.2,gri);
cutie(16.15,AXA,6.12,4.6,3.4,.14,mat(0x556070),false);
[2.2,5.5].forEach(function(w){
for(var b=0;b<5;b++) cutie(14.3+b*1.1,w,6.7,.06,.06,1.05,galben);
cutie(16.35,w,7.25,4.4,.06,.08,galben);
});
/* turn elevator cu ferestre de vizitare rosii */
cutie(16.15,6.9,3.9,1.1,.95,7.8,gri);
for(var e=1.2;e<7;e+=1.1) cutie(16.15,6.42,e,.6,.05,.5,mat(0xA8313A));
cutie(16.15,6.9,8.0,1.5,1.3,.9,gri);
/* ciclon + racord spre desprafuitor */
var cic=new THREE.Mesh(new THREE.CylinderGeometry(.6,.22,1.9,12),gri);
cic.position.copy(poz(13.9,7.7,5.2)); grup.add(cic);
/* dulap electric mare, pe pardoseala */
cutie(11.2,8.9,1.15,1.7,1.1,2.3,mat(0x565E68));
etich('BLASTING TUNNEL',16.15,AXA,9.3,11);
etich('6 x TURBINES 11 kW',16.25,8.6,8.9,9);
})();
/* ---------- LINIA DE GAURIRE + FIERASTRAU ---------- */
(function(){
var verde=mat(0x7BA88A,{r:.55}), inchis=mat(0x4F7861,{r:.55});
// portal gaurire: 2 coloane + traversa + 3 unitati de ax
cutie(48.5,1.5,1.6,2.6,1.0,3.2,verde);
cutie(48.5,6.2,1.6,2.6,1.0,3.2,verde);
cutie(48.5,AXA,3.5,2.6,5.6,.9,verde); // traversa
cutie(48.5,AXA,2.6,1.0,1.0,1.2,inchis); // unitate verticala
var axV=new THREE.Mesh(new THREE.CylinderGeometry(.12,.12,1.1,10),mat(0xD8DEE4,{m:.6}));
axV.position.copy(poz(48.5,AXA,1.75)); grup.add(axV);
var axH1=new THREE.Mesh(new THREE.CylinderGeometry(.12,.12,1.0,10),mat(0xD8DEE4,{m:.6}));
axH1.rotation.x=Math.PI/2; axH1.position.copy(poz(48.5,2.6,1.35)); grup.add(axH1);
var axH2=axH1.clone(); axH2.position.copy(poz(48.5,5.1,1.35)); grup.add(axH2);
ANIM.axV=axV; ANIM.axH1=axH1; ANIM.axH2=axH2;
ANIM.axVy=axV.position.y; ANIM.axH1z=axH1.position.z; ANIM.axH2z=axH2.position.z;
cutie(48.5,1.7,1.5,1.0,.8,1.1,inchis); cutie(48.5,6.0,1.5,1.0,.8,1.1,inchis); // unitati orizontale
// magazii de scule (discuri)
for(var i=0;i<3;i++){
var d=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,.15,14),mat(0x395143));
d.position.copy(poz(47.2,1.6+i*2.5,3.1)); grup.add(d);
}
cutie(46,7.6,1.0,1.6,1.2,2.0,mat(0x5E6670)); // dulap comanda
cutie(50.6,6.9,.5,3.2,.8,.9,inchis); // transportor span
// fierastrau cu inclinare
cutie(57.5,AXA,1.5,2.4,4.6,3.0,verde);
var lama=new THREE.Mesh(new THREE.BoxGeometry(.18,2.4,2.6),inchis);
lama.position.copy(poz(57.5,AXA,2.9)); lama.rotation.y=.5; grup.add(lama);
cutie(59.2,6.4,.9,1.2,.9,1.8,mat(0x5E6670));
etich('DRILLING + CUTTING',52,AXA,7.2,12);
})();
/* brat de alimentare (carucior cu clema) */
var carucior=new THREE.Group();
carucior.add(new THREE.Mesh(new THREE.BoxGeometry(1.2,.7,.8),mat(0x4F7861)));
var clema=new THREE.Mesh(new THREE.BoxGeometry(.25,.5,1.2),mat(0x395143));
clema.position.set(.4,.5,-.7); carucior.add(clema);
carucior.position.copy(poz(33,6.5,1.0)); scena.add(carucior);
cutie(46.7,6.5,.35,29,.35,.25,mat(0x39424E)); // sina carucior
/* ---------- SPATIU DE DEPOZITARE MATERIAL BRUT ---------- */
(function(){
for(var i=0;i<4;i++){
var a=new THREE.Mesh(new THREE.BoxGeometry(.25,1.6,5),mat(0x8E99A6));
a.position.copy(poz(2+i*3,12.8,.8)); grup.add(a);
}
var matBara=mat(0x8A5A3C,{r:.7,m:.3});
for(var j=0;j<3;j++){
var b=new THREE.Mesh(new THREE.BoxGeometry(12,.4,.35),matBara);
b.position.copy(poz(6.5,11.6+j*1.1,1.15)); b.castShadow=true; grup.add(b);
}
etich('STORAGE AREA',6.5,12.8,3.4,10);
})();
/* ---------- ZONE SUDURA ---------- */
(function(){
var galben=mat(0xD8C271,{o:.85});
var zone=[[65,72,'WELDING 1'],[57,64,'WELDING 2'],[49,56,'WELDING 3']];
for(var z=0;z<3;z++){
var Z=zone[z];
cutie((Z[0]+Z[1])/2,31.8,.12,Z[1]-Z[0],18,.24,galben,false);
// masa de sudura + aparat cu butelie
var mL=(Z[0]+Z[1])/2;
cutie(mL,30,1.0,3.2,1.4,.18,mat(0x50565E,{m:.3}));
for(var p=0;p<4;p++) cutie(mL+(p<2?-1.4:1.4),29.4+(p%2)*1.2,.45,.16,.16,.9,mat(0x3A3F46));
cutie(mL-2.6,28.6,.55,.7,.5,1.0,mat(0x8A2F2F));
var but=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,1.1,10),mat(0x5E7889));
but.position.copy(poz(mL-2.1,28.3,.6)); grup.add(but);
etich(Z[2],mL,31.8,4.6,9);
}
// rotatoare cu lant (2 seturi = 4 unitati)
var seturi=[[56.3,58.3],[64.3,66.3]];
for(var s=0;s<2;s++){
for(var u=0;u<2;u++){
var L=seturi[s][u];
cutie(L,24.5,.5,1.4,1.2,1.0,mat(0x4F7861));
var lant=new THREE.Mesh(new THREE.TorusGeometry(.55,.09,8,20),mat(0x39424E,{m:.4}));
lant.position.copy(poz(L,24.5,1.35)); grup.add(lant);
}
}
// filtre Tower cu brat perforat
var tw=[[64.3,41.6],[49.3,41.6]];
for(var t=0;t<2;t++){
cutie(tw[t][0],tw[t][1],2.0,1.85,1.1,4.0,mat(0x6E7883,{r:.5}));
var brat=new THREE.Mesh(new THREE.CylinderGeometry(.22,.22,4.5,10),mat(0x828B96));
brat.rotation.x=Math.PI/2; brat.position.copy(poz(tw[t][0],39.2,3.6)); grup.add(brat);
}
etich('FUME FILTERS',56.8,41.6,5.4,9);
})();
/* ---------- CAMERE (sablare + vopsire) ---------- */
function camba(L1,L2,W1,W2,nume,vopsire){
var alb=mat(0xF3F2F4,{o:.38,r:.7}), rosu=mat(0xA8313A,{r:.55}), albastru=mat(0x3B6EC0,{r:.5});
var h=4, Lm=(L1+L2)/2, Wm=(W1+W2)/2;
cutie(Lm,W1,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,W2,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,Wm,h+.05,L2-L1,W2-W1,.14,mat(0xF3F2F4,{o:.55}),false);
for(var l=L1;l<=L2+.01;l+=2.17){
cutie(l,W1,h/2,.2,.24,h,rosu); cutie(l,W2,h/2,.2,.24,h,rosu);
}
[0.12,h-0.12].forEach(function(y){
cutie(Lm,W1,y,L2-L1,.26,.2,rosu,false); cutie(Lm,W2,y,L2-L1,.26,.2,rosu,false);
});
[[L1,-1],[L2,1]].forEach(function(cap){
cutie(cap[0],W1+.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=cap[1]*0.5;
cutie(cap[0],W2-.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=-cap[1]*0.5;
cutie(cap[0],Wm,h-.25,.2,W2-W1,.5,rosu,false);
});
if(vopsire){
cutie(L1+2,Wm,h+.65,2.2,1.6,1.2,mat(0x7E8794));
var tOr=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,(L2-L1)*.55,12),albastru);
tOr.rotation.z=Math.PI/2; tOr.position.copy(poz(Lm-1,Wm,h+.75)); grup.add(tOr);
var tVe=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,2.6,12),albastru);
tVe.position.copy(poz(L2-1.5,Wm,h+1.9)); grup.add(tVe);
}else{
cutie(Lm,W1-0.9,1.4,3.2,1.4,2.8,mat(0x7E8794));
var cos2=new THREE.Mesh(new THREE.CylinderGeometry(.2,.2,2.4,10),mat(0x8B93A0));
cos2.position.copy(poz(Lm,W1-0.9,3.9)); grup.add(cos2);
cutie(Lm,Wm,.06,L2-L1-1,1.1,.12,mat(0x3E4650));
var tOr2=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,3.4,12),albastru);
tOr2.rotation.x=Math.PI/2; tOr2.position.copy(poz(Lm,W1+0.8,h+.7)); grup.add(tOr2);
}
etich(nume,Lm,Wm,5.6,11);
}
camba(22.5,35.5,24.3,28.3,'BLASTING CHAMBER',false);
camba(22.5,35.5,30.8,34.8,'PAINT BOOTH',true);
cutie(11,34,.11,14,13,.22,mat(0x8E99A6,{o:.75}),false);
etich('STORAGE',11,34,2.6,10);
// piese finite vopsite
var matFin=mat(0xB3352E,{r:.55,m:.2});
for(var pf=0;pf<2;pf++){
var fin=new THREE.Mesh(new THREE.BoxGeometry(10,.5,.4),matFin);
fin.position.copy(poz(10,31.5+pf*1.6,.5)); fin.castShadow=true; grup.add(fin);
}
/* ---------- PODURI RULANTE ---------- */
var matCran=mat(0xC9A23F,{r:.5}), matCran2=mat(0xA8862F,{r:.5});
function grindaCale(W){cutie(HALA_L/2,W,7.55,HALA_L,.4,.4,matCran2,false);}
grindaCale(.7); grindaCale(20.9); grindaCale(22.2); grindaCale(42.1);
function facePod(W1,W2){
var g=new THREE.Group(); var span=W2-W1;
[ -0.9, 0.9 ].forEach(function(dx){
var gi=new THREE.Mesh(new THREE.BoxGeometry(.55,.7,span),matCran);
gi.position.set(dx,0, 0); gi.castShadow=true; g.add(gi);
});
[-span/2+0.3,span/2-0.3].forEach(function(dz){
var tr=new THREE.Mesh(new THREE.BoxGeometry(2.6,.6,.7),matCran2);
tr.position.set(0,0,dz); g.add(tr);
});
var trolley=new THREE.Mesh(new THREE.BoxGeometry(1.5,.7,1.3),matCran2);
trolley.position.set(0,.7,0); g.add(trolley);
g.userData={W1:W1,W2:W2,trolley:trolley,L:30,Wt:(W1+W2)/2};
scena.add(g); return g;
}
var podE=facePod(0.9,20.7); podE.userData.L=8; podE.userData.Wt=8;
var podV=facePod(22.4,42.0); podV.userData.L=45; podV.userData.Wt=38;
var Y_POD=7.9;
// cabluri + traversa de ridicare
function faceCablu(){
var geo=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);
var ln=new THREE.Line(geo,new THREE.LineBasicMaterial({color:0xD8DEE4}));
ln.visible=false; scena.add(ln); return ln;
}
var cabluri=[faceCablu(),faceCablu(),faceCablu(),faceCablu()];
var traversa=new THREE.Mesh(new THREE.BoxGeometry(8,.22,.3),matCran2);
traversa.visible=false; scena.add(traversa);
/* ---------- DETALII v3 (documentate) ---------- */
function teava(a,b,r,c){
var d=new THREE.Vector3().subVectors(b,a), len=d.length();
var m=new THREE.Mesh(new THREE.CylinderGeometry(r,r,len,8),mat(c||0x8B93A0));
m.position.copy(a).addScaledVector(d,.5);
m.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.clone().normalize());
scena.add(m); return m;
}
/* tunel: perdele de cauciuc la intrare/iesire (etansare labirint) */
[[12.55],[19.95]].forEach(function(cap){
for(var w=2.45;w<=5.3;w+=.32){
var f=new THREE.Mesh(new THREE.BoxGeometry(.05,1.35,.27),mat(0x23262B,{r:.9}));
f.position.copy(poz(cap[0],w,1.75)); grup.add(f);
}
});
/* tevi de dozare abraziv: separator -> fiecare turbina */
var sep=poz(16.25,5.9,6.15);
[[15.1,1.35,4.0],[16.25,1.35,3.2],[17.4,1.35,4.0],[15.1,6.33,4.0],[16.25,6.33,3.2],[17.4,6.33,4.0]]
.forEach(function(p){ teava(sep,poz(p[0],p[1],p[2]+.55),.07); });
/* duct camera -> desprafuitor + snec longitudinal sub role */
teava(poz(16.9,5.6,4.55),poz(15.4,8.3,4.3),.26);
cutie(16.5,AXA,.12,7.4,.7,.24,mat(0x39424E));
/* sistem de curatare la iesire: perie nylon + suflanta */
var perie=new THREE.Mesh(new THREE.CylinderGeometry(.2,.2,3.0,12),mat(0x6E5A3A,{r:.9}));
perie.rotation.x=Math.PI/2; perie.position.copy(poz(19.55,AXA,2.1)); grup.add(perie);
cutie(19.55,6.3,2.7,.9,.9,.9,mat(0x556070));
teava(poz(19.55,5.85,2.7),poz(19.55,4.6,2.35),.12);
/* aparatoare de lant pe o parte a fiecarei mese cu role */
[[0.5,12.5],[20,32],[32,44],[61.5,73.5]].forEach(function(t){
cutie((t[0]+t[1])/2,AXA+1.12,.86,t[1]-t[0],.1,.34,mat(0x2E353D));
});
/* perdea de alice in camera (animata) */
var perdea=new THREE.Mesh(new THREE.BoxGeometry(.14,3.3,3.1),
new THREE.MeshBasicMaterial({color:0xD8C9A0,transparent:true,opacity:0}));
perdea.position.copy(poz(16.25,AXA,2.3)); scena.add(perdea); ANIM.perdea=perdea;
/* portal gaurire: laser de zero + bacuri de strangere + pupitru mobil */
var laser=new THREE.Mesh(new THREE.BoxGeometry(.03,.03,3.3),
new THREE.MeshBasicMaterial({color:0xFF3B30}));
laser.position.copy(poz(47.1,AXA,1.06)); scena.add(laser);
cutie(48.5,2.75,1.15,.5,.35,.7,mat(0x394450)); cutie(48.5,4.95,1.15,.5,.35,.7,mat(0x394450));
cutie(44.6,6.9,.75,.7,.6,1.4,mat(0x5E6670));
/* fierastrau: descarcare magnetica */
cutie(60.2,6.1,1.9,.35,.35,3.6,mat(0x4F7861));
cutie(59.7,5.2,3.4,1.6,.3,.3,mat(0x4F7861));
cutie(59.2,4.4,2.9,.7,.5,.35,mat(0x8A2F2F));
/* ecrane de protectie sudura in jurul fiecarei zone */
[[68.5],[60.5],[52.5]].forEach(function(z){
var mE=mat(0x7A2E2E,{o:.45,r:.85});
cutie(z[0],29.1,1.1,5.0,.08,2.2,mE,false);
cutie(z[0],34.5,1.1,5.0,.08,2.2,mE,false);
cutie(z[0]+2.6,31.8,1.1,.08,5.5,2.2,mE,false);
});
/* rastel butelii linga zona 3 */
cutie(46.6,29.5,.75,1.6,.7,1.5,mat(0x4A5560));
for(var bb=0;bb<3;bb++){
var bt=new THREE.Mesh(new THREE.CylinderGeometry(.15,.15,1.3,10),mat(0x5E7889));
bt.position.copy(poz(46.2+bb*.45,29.5,.75)); grup.add(bt);
}
/* camera sablare: unitate de recuperare RU + benzi de lumina */
cutie(36.6,25.0,1.15,1.5,1.2,2.3,mat(0x7E8794));
teava(poz(35.8,25.4,2.1),poz(34.9,26.3,1.6),.14);
[[26.3],[32.8]].forEach(function(w){
[25.5,29,32.5].forEach(function(l){
var lm=new THREE.Mesh(new THREE.BoxGeometry(2.2,.06,.2),
new THREE.MeshBasicMaterial({color:0xFFF6E0}));
lm.position.copy(poz(l,w[0],3.86)); scena.add(lm);
});
});
/* incinta vopsire: perete aspirant cu grila la capatul de iesire */
cutie(22.75,32.8,1.9,.16,3.6,3.4,mat(0x5A6470,{o:.85}),false);
for(var gz=0;gz<5;gz++) cutie(22.83,31.35+gz*.72,1.9,.05,.08,3.0,mat(0x8B93A0),false);
/* poduri: carucior cu tambur si carlig */
[podE,podV].forEach(function(p){
var tb=new THREE.Mesh(new THREE.CylinderGeometry(.22,.22,.7,10),mat(0x6E5A20));
tb.rotation.z=Math.PI/2; tb.position.set(0,1.15,0); p.userData.trolley.add(tb);
});
/* referinta izometrica: palnii conice sub desprafuitoare + scara de acces */
[[13.6,7.4],[13.6,9.4],[17.2,7.4],[17.2,9.4]].forEach(function(pp){
cutie(pp[0],pp[1],0.6,.18,.18,1.2,mat(0x6A7280));
});
var palnie=new THREE.Mesh(new THREE.CylinderGeometry(1.05,.22,1.5,4),mat(0x6A7280));
palnie.rotation.y=Math.PI/4; palnie.position.copy(poz(15.4,8.4,-0.05)); grup.add(palnie);
cutie(12.4,9.6,2.0,1.8,1.6,4.0,mat(0x7E8794,{r:.55}));
var palnie2=new THREE.Mesh(new THREE.CylinderGeometry(.8,.2,1.3,4),mat(0x6A7280));
palnie2.rotation.y=Math.PI/4; palnie2.position.copy(poz(12.4,9.6,0)); grup.add(palnie2);
cutie(17.1,7.3,2.6,.08,.5,5.2,mat(0x3E8E4E));
for(var tt=0.5;tt<5;tt+=.45) cutie(17.1,7.3,tt,.3,.42,.05,mat(0x3E8E4E));
/* ---------- OPERATORI ---------- */
function persoana(cCasca,cVesta,cPant){
var g=new THREE.Group();
var mPant=mat(cPant||0x39424E,{r:.8}), mVesta=mat(cVesta||0xD97B29,{r:.8}), mPiele=mat(0xC9A183,{r:.8});
[-0.09,0.09].forEach(function(x){
var p=new THREE.Mesh(new THREE.BoxGeometry(.15,.75,.15),mPant);
p.position.set(x,.375,0); p.castShadow=true; g.add(p);
});
var tors=new THREE.Mesh(new THREE.BoxGeometry(.42,.6,.24),mVesta);
tors.position.set(0,1.05,0); tors.castShadow=true; g.add(tors);
var cap=new THREE.Mesh(new THREE.SphereGeometry(.13,10,10),mPiele);
cap.position.set(0,1.5,0); g.add(cap);
var casca=new THREE.Mesh(new THREE.SphereGeometry(.15,10,8,0,Math.PI*2,0,Math.PI/2),mat(cCasca,{r:.5}));
casca.position.set(0,1.52,0); g.add(casca);
function brat(x){
var piv=new THREE.Group(); piv.position.set(x,1.32,0);
var b=new THREE.Mesh(new THREE.BoxGeometry(.11,.55,.11),mVesta);
b.position.set(0,-.27,0); b.castShadow=true; piv.add(b); g.add(piv); return piv;
}
var bs=brat(-.27), bd=brat(.27);
bs.rotation.z=.15; bd.rotation.z=-.15;
return {g:g,bs:bs,bd:bd,tors:tors};
}
function pune(om,L,W,rot){om.g.position.copy(poz(L,W,0));om.g.rotation.y=rot||0;scena.add(om.g);return om;}
/* supraveghetor sablare - la pupitrul de langa tunel */
cutie(11.6,6.6,.85,.5,.5,.5,mat(0x5E6670)); // pupitru
var opSab=pune(persoana(0xF2EDE7,0xD97B29),11.6,7.4,Math.PI);
/* operator gaurire+debitare - la dulapul de comanda */
var opGaur=pune(persoana(0xF2EDE7,0xD97B29),45.0,7.3,Math.PI+.4);
/* sudori: 2 pe fiecare zona */
var sudori=[];
[[68.5],[60.5],[52.5]].forEach(function(z,zi){
var s1=pune(persoana(0xB03030,0x4A5560),z[0]-1.7,30.6,0);
var s2=pune(persoana(0xB03030,0x4A5560),z[0]+1.7,33.0,Math.PI);
sudori.push([s1,s2]);
});
/* pistolete sudura cu varf luminos, doar zona 1 */
function pistolet(){
var g=new THREE.Group();
var corp=new THREE.Mesh(new THREE.BoxGeometry(.08,.3,.08),mat(0x2A2E33));
corp.position.y=-.15; g.add(corp);
var varf=new THREE.Mesh(new THREE.SphereGeometry(.05,8,8),
new THREE.MeshBasicMaterial({color:0xBFDcFF}));
varf.position.y=-.34; g.add(varf); g.userData.varf=varf;
scena.add(g); g.visible=false; return g;
}
var pist1=pistolet(), pist2=pistolet();
/* sablator in camera - cu furtun si duza */
var opCamSab=pune(persoana(0x8899AA,0x8899AA,0x707A85),27.2,27.7,Math.PI*1.5);
var jetSab=new THREE.Mesh(new THREE.ConeGeometry(.5,2.6,10,1,true),
new THREE.MeshBasicMaterial({color:0xD8C9A0,transparent:true,opacity:.28,side:THREE.DoubleSide}));
jetSab.visible=false; scena.add(jetSab);
var furtunSab=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,4.5,8),mat(0x2A2E33));
furtunSab.visible=false; scena.add(furtunSab);
/* vopsitor in incinta - combinezon alb, pistol cu jet */
var opVops=pune(persoana(0xF2EDE7,0xE8E4DE,0xD8D4CE),31.5,33.9,Math.PI*1.5);
var jetVops=new THREE.Mesh(new THREE.ConeGeometry(.42,2.0,10,1,true),
new THREE.MeshBasicMaterial({color:0xE0483C,transparent:true,opacity:.32,side:THREE.DoubleSide}));
jetVops.visible=false; scena.add(jetVops);
/* ---------- PROFILUL, cu evolutie pe etape ---------- */
var grupProfil=new THREE.Group(); scena.add(grupProfil);
var CUL={
rugina:new THREE.Color(0x8E4B30), /* otel laminat, oxidat */
sa3: new THREE.Color(0xC6CCD4), /* curatat SA 3, metal alb */
cordon:new THREE.Color(0x24242A), /* cordon de sudura brut */
grund: new THREE.Color(0xA85440) /* grund sudabil */
};
var matProfil=new THREE.MeshStandardMaterial({color:CUL.rugina.clone(),roughness:.92,metalness:.15});
matProfil.emissive=new THREE.Color(CUL.rugina); matProfil.emissiveIntensity=.16;
var matCordon=new THREE.MeshStandardMaterial({color:CUL.cordon.clone(),roughness:.75,metalness:.3});
var matGaura =new THREE.MeshStandardMaterial({color:0x15151A,roughness:1});
function grinda(len,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var t1=new THREE.Mesh(new THREE.BoxGeometry(len,.05,.32),m);
var t2=t1.clone(); var inima=new THREE.Mesh(new THREE.BoxGeometry(len,.9,.03),m);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true;g.add(p);});
return g;
}
var profil=grinda(12); grupProfil.add(profil);
/* --- TABLA: varianta alternativa de piesa --- */
function facTabla(len,lat,gros,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var pl=new THREE.Mesh(new THREE.BoxGeometry(len,gros,lat),m);
pl.castShadow=true; g.add(pl);
return g;
}
var tablaGrup=new THREE.Group(); tablaGrup.visible=false; grupProfil.add(tablaGrup);
var tablaCorp=facTabla(12,1.5,.06); tablaGrup.add(tablaCorp);
/* gauri in tabla, dispuse pe doua randuri */
var tablaGauri=new THREE.Group(); tablaGauri.visible=false; tablaGrup.add(tablaGauri);
[-5.0,-3.5,-2.0,-0.5,1.0,2.5,4.0,5.3].forEach(function(x){
[-0.45,0.45].forEach(function(z){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.085,.085,.09,12),matGaura);
h.position.set(x,0,z); tablaGauri.add(h);
});
});
/* decupaj dreptunghiular, ca la debitarea de tabla */
[-5.95,5.95].forEach(function(x){
var d=new THREE.Mesh(new THREE.BoxGeometry(.22,.08,.5),matGaura);
d.position.set(x,0,0); tablaGauri.add(d);
});
/* nervuri de rigidizare sudate pe tabla + cordoanele lor */
var tablaNerv=new THREE.Group(); tablaNerv.visible=false; tablaGrup.add(tablaNerv);
var tablaCord=new THREE.Group(); tablaCord.visible=false; tablaGrup.add(tablaCord);
[-4.2,-1.4,1.4,4.2].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.05,.42,1.4),matProfil);
n.position.set(x,.24,0); n.castShadow=true; tablaNerv.add(n);
[.03,-.03].forEach(function(dz){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,1.4,8),matCordon);
c.rotation.x=Math.PI/2; c.position.set(x+dz*1.6,.045,0); tablaCord.add(c);
});
});
/* bordura sudata pe cele doua laturi lungi */
[-.72,.72].forEach(function(z){
var b=new THREE.Mesh(new THREE.BoxGeometry(11.6,.30,.05),matProfil);
b.position.set(0,.18,z); b.castShadow=true; tablaNerv.add(b);
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,11.6,8),matCordon);
c.rotation.z=Math.PI/2; c.position.set(0,.04,z); tablaCord.add(c);
});
/* --- BARE IN PARALEL LA SABLARE ---
Fereastra utila a tunelului are 1.700 x 1.200 mm. Un profil de 306 mm latime
intra de trei ori pe latime, cu spatiu intre bare. La linia de gaurire bratul
de alimentare preia o singura bara, deci barele paralele dispar dupa tunel. */
var N_PARALEL=1, PARALELE=[];
(function(){
[-0.62, 0.62].forEach(function(dz){
var g=new THREE.Group();
var t1=new THREE.Mesh(new THREE.BoxGeometry(12,.05,.32),matProfil);
var t2=t1.clone();
var inima=new THREE.Mesh(new THREE.BoxGeometry(12,.9,.03),matProfil);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true; g.add(p);});
g.position.z=dz; g.visible=false;
grupProfil.add(g);
PARALELE.push(g);
});
})();
/* comutatorul profil / tabla */
var MOD_TABLA=false;
/* --- gauri si frezari, apar dupa modulul de gaurire --- */
var gauri=new THREE.Group(); gauri.visible=false; grupProfil.add(gauri);
[-4.6,-3.1,-1.6,1.6,3.1,4.6].forEach(function(x){
[-0.28,0.28].forEach(function(y){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.075,.075,.06,10),matGaura);
h.rotation.x=Math.PI/2; h.position.set(x,y,0); gauri.add(h);
});
/* gauri si pe talpi */
[.478,-.478].forEach(function(y){
var h2=new THREE.Mesh(new THREE.CylinderGeometry(.06,.06,.07,10),matGaura);
h2.position.set(x,y,.11); gauri.add(h2);
});
});
/* frezari la capete */
[-5.85,5.85].forEach(function(x){
var fr=new THREE.Mesh(new THREE.BoxGeometry(.3,.34,.05),matGaura);
fr.position.set(x,0,0); gauri.add(fr);
});
/* --- subansamblu: traverse, nervuri si cordoane de sudura --- */
var traverse=new THREE.Group(); traverse.visible=false; grupProfil.add(traverse);
[-3.6,3.6].forEach(function(dx){
var t=grinda(3.6); t.rotation.y=Math.PI/2; t.position.set(dx,0,0); traverse.add(t);
});
var nervuri=new THREE.Group(); nervuri.visible=false; grupProfil.add(nervuri);
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.04,.86,.3),matProfil);
n.position.set(x,0,0); n.castShadow=true; nervuri.add(n);
});
var cordoane=new THREE.Group(); cordoane.visible=false; grupProfil.add(cordoane);
function cordon(x,y,z,len,rotY,rotZ){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,len,8),matCordon);
c.rotation.z=Math.PI/2; if(rotY) c.rotation.y=rotY; if(rotZ) c.rotation.z+=rotZ;
c.position.set(x,y,z); cordoane.add(c); return c;
}
/* cordoane la nervuri, pe ambele talpi si pe ambele fete */
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
[.44,-.44].forEach(function(y){
[.15,-.15].forEach(function(z){ cordon(x,y,z,.3,Math.PI/2); });
});
});
/* cordoane la imbinarea traverselor */
[-3.6,3.6].forEach(function(x){
[.44,-.44].forEach(function(y){ cordon(x,y,0,.34,Math.PI/2); });
[.3,-.3].forEach(function(z){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,.9,8),matCordon);
c.position.set(x,0,z); cordoane.add(c);
});
});
/* --- rotator de sudura: piesa se sprijina si se roteste --- */
var rotator=new THREE.Group(); rotator.visible=false; scena.add(rotator);
[-2.6,2.6].forEach(function(dx){
var cadru=new THREE.Group();
var stalpM=new THREE.Mesh(new THREE.BoxGeometry(.34,2.5,.5),mat(0xE8E4DE,{r:.6}));
stalpM.position.set(dx,1.25,-1.5); cadru.add(stalpM);
var stalpS=stalpM.clone(); stalpS.position.set(dx,1.25,1.5); cadru.add(stalpS);
var trav=new THREE.Mesh(new THREE.BoxGeometry(.3,.3,3.4),mat(0xE8E4DE,{r:.6}));
trav.position.set(dx,2.5,0); cadru.add(trav);
var baza=new THREE.Mesh(new THREE.BoxGeometry(.9,.22,3.9),mat(0xB8B2AC,{r:.7}));
baza.position.set(dx,.11,0); cadru.add(baza);
[-1.5,1.5].forEach(function(dz){
var brat=new THREE.Mesh(new THREE.BoxGeometry(.22,.9,.22),mat(0xC0392B,{r:.55}));
brat.position.set(dx,2.05,dz); cadru.add(brat);
});
var lant=new THREE.Mesh(new THREE.TorusGeometry(.95,.055,7,22),mat(0x3A3F46,{m:.5,r:.4}));
lant.rotation.y=Math.PI/2; lant.position.set(dx,1.15,0); cadru.add(lant);
rotator.add(cadru);
});
var lumProces=new THREE.PointLight(0xffffff,0,18); scena.add(lumProces);
/* ================= SCANTEI ================= */
function facePartic(n,culoare,marime){
var g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var m=new THREE.PointsMaterial({color:culoare,size:marime,transparent:true,opacity:.95,
depthWrite:false,blending:THREE.AdditiveBlending});
var pts=new THREE.Points(g,m); pts.visible=false; scena.add(pts);
return {pts:pts,pos:p,vel:new Float32Array(n*3),viata:new Float32Array(n),n:n};
}
var SC_SUD=facePartic(160,0xBFE0FF,.16);
var SC_GAUR=facePartic(120,0xFFC46B,.15);
var SC_SABL=facePartic(220,0xE8D6AE,.10);
function emite(S,origine,forta,imprastiere){
for(var i=0;i<S.n;i++){
if(S.viata[i]<=0&&Math.random()<.22){
S.pos[i*3]=origine.x+(Math.random()-.5)*imprastiere;
S.pos[i*3+1]=origine.y+(Math.random()-.5)*.2;
S.pos[i*3+2]=origine.z+(Math.random()-.5)*imprastiere;
S.vel[i*3]=(Math.random()-.5)*forta;
S.vel[i*3+1]=Math.random()*forta*1.5;
S.vel[i*3+2]=(Math.random()-.5)*forta;
S.viata[i]=.4+Math.random()*.6;
}
}
}
function actualizeazaPartic(S,dt){
var viu=false;
for(var i=0;i<S.n;i++){
if(S.viata[i]>0){
S.viata[i]-=dt; viu=true;
S.vel[i*3+1]-=9.8*dt*.35;
S.pos[i*3]+=S.vel[i*3]*dt; S.pos[i*3+1]+=S.vel[i*3+1]*dt; S.pos[i*3+2]+=S.vel[i*3+2]*dt;
if(S.pos[i*3+1]<.05){S.pos[i*3+1]=.05; S.vel[i*3+1]*=-.35;}
} else { S.pos[i*3+1]=-99; }
}
S.pts.geometry.attributes.position.needsUpdate=true;
S.pts.visible=viu;
}
/* ---------- ETAPE ---------- */
var HL=1.35, HS=0.65;
var ETAPE=[
{n:'Raw material storage area',d:'The 12 m HEB profile waits in the storage area, next to the entry gate.',
t:5,c:[[6.5,12.8,1.3,'rastel'],[6.5,12.8,1.3,'rastel']]},
{n:'Transfer to the line',d:'The overhead crane in the east bay places the profile on the entry rollers.',
t:6,c:[[6.5,12.8,1.3,'rastel'],[6.5,AXA,HL,'tunel']],arc:2.6,pod:'E'},
{n:'Blasting in the tunnel',d:'Six 11 kW turbines clean the profile to SA 2.5 in a single pass.',
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:'To the drill line',d:'The cleaned profile moves onto the entry table, where the feed arm picks it up.',
t:5,c:[[26,AXA,HL,'tunel'],[40,AXA,HL,'gaurire']]},
{n:'CNC drilling on three faces',d:'One vertical and two horizontal spindles drill the web and the flanges at once.',
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:'Bevel cutting',d:'The tilting saw cuts at 11.95 m, at the angle the joint requires.',
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:'Exit rollers',d:'The processed profile reaches the end of the line.',
t:4,c:[[58,AXA,HL,'gaurire'],[67.5,AXA,HL,'gaurire']]},
{n:'Transfer to welding',d:'The overhead cranes hand the part over across the central aisle, from east to west.',
t:9,c:[[67.5,AXA,HL,'gaurire'],[68.5,31.8,HS,'sud1']],arc:3.6,pod:'EV'},
{n:'Welding the assembly',d:'Three work zones with pulsed MIG/MAG. The rotators turn the part without a crane.',
t:9,c:[[68.5,31.8,HS,'sud1'],[68.5,31.8,HS,'sud1']],
lum:function(t){var o=OFU('sud1');
return {p:poz(68.5+o.L+(Math.random()-.5)*4,31.8+o.W,1.4),c:0x9FC6FF,i:Math.random()>.35?4.5:.6};}},
{n:'Transport to the chambers',d:'The west overhead crane lowers the assembly into the manoeuvring aisle.',
t:7,c:[[68.5,31.8,HS,'sud1'],[42,26.3,HS,'camsab']],arc:2.4,pod:'V'},
{n:'Blasting chamber',d:'Welds and cut edges are cleaned before coating. Doors at both ends.',
t:8,c:[[42,26.3,HS,'camsab'],[29,26.3,HS,'camsab']],
cul:function(t){return CUL.sa3;},
lum:function(t){var o=OFU('camsab');
return t>.5?{p:poz(29+o.L,26.3+o.W,2.4),c:0xFFC86B,i:2.2+Math.random()*1.4}:null;}},
{n:'Paint booth',d:'Longitudinal airflow, forced drying. The part leaves through the opposite end.',
t:10,c:[[29,26.3,HS,'camsab'],[38.2,26.3,HS,'camsab'],[38.2,32.8,HS,'camvops'],[29,32.8,HS,'camvops']],
cul:function(t){var k=Math.min(Math.max((t-.6)/.32,0),1);return CUL.sa3.clone().lerp(CUL.grund,k);},
lum:function(t){var o=OFU('camvops');
return t>.58?{p:poz(29+o.L,32.8+o.W,2.4),c:0xFF8A7A,i:1.8}:null;}},
{n:'Storage and dispatch',d:'The painted assembly leaves through the second gate. The U-shaped flow is closed.',
t:9,c:[[29,32.8,HS,'camvops'],[18,32.8,HS,'camvops'],[11,34,HS,'depozit'],[-3,29.35,HS,0]]}
];
function OFU(id){
if(!id||typeof UNITATI==='undefined')return {L:0,W:0};
for(var i=0;i<UNITATI.length;i++) if(UNITATI[i].id===id)
return {L:UNITATI[i].off.x, W:-UNITATI[i].off.z};
return {L:0,W:0};
}
function pctO(w){var o=OFU(w[3]);return [w[0]+o.L,w[1]+o.W,w[2]];}
/* durate reale in minute, derivate din avansul de 1 m/min pe tunel si pe linia de gaurire */
var MIN_REALE=[3,4,20,12,12,5,6,5,90,6,25,20,5];
var T_REAL_TOTAL=MIN_REALE.reduce(function(a,b){return a+b;},0);
var SCARA_MIN=0.34; /* o secunda de animatie = ~3 minute reale */
ETAPE.forEach(function(e,i){ e.tPrezentare=e.t; e.min=MIN_REALE[i]; });
var TIMP_REAL=false;
function aplicaScaraTimp(){
ETAPE.forEach(function(e,i){ e.t = TIMP_REAL ? e.min*SCARA_MIN : e.tPrezentare; });
T_TOTAL=0; praguri=[];
ETAPE.forEach(function(e){ praguri.push([T_TOTAL,T_TOTAL+e.t]); T_TOTAL+=e.t; });
timp=Math.min(timp,T_TOTAL);
}
var T_TOTAL=0, praguri=[];
ETAPE.forEach(function(e){praguri.push([T_TOTAL,T_TOTAL+e.t]);T_TOTAL+=e.t;});
var cont=document.getElementById('progres-etape');
ETAPE.forEach(function(){var d=document.createElement('div');d.className='pas';cont.appendChild(d);});
var pasi=[].slice.call(cont.children);
function caleP(cale,t,arc){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
var p=poz(a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt, a[2]+(b[2]-a[2])*lt);
if(arc) p.y+=Math.sin(t*Math.PI)*arc;
return p;
}
function caleLW(cale,t){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
return [a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt];
}
/* ---------- animatie ---------- */
var timp=0, ruleaza=false, urmarire=false;
var btnPlay=document.getElementById('btn-play'), scrub=document.getElementById('scrub'),
viteza=document.getElementById('viteza'), btnUrm=document.getElementById('btn-urmarire');
function miscaPod(pod,Lt,Wt,activ,dt){
var u=pod.userData, k=Math.min(dt*2.2,1);
u.L+=(Lt-u.L)*k; u.Wt+=(Wt-u.Wt)*k;
var Wc=Math.min(Math.max(u.Wt,u.W1+1),u.W2-1);
pod.position.copy(poz(u.L,(u.W1+u.W2)/2,Y_POD));
u.trolley.position.z=(HALA_W/2-Wc)-pod.position.z;
return {L:u.L,W:Wc};
}
function cabluriLa(pod,pTinta){
var u=pod.userData;
var tr=new THREE.Vector3(); u.trolley.getWorldPosition(tr); tr.y-=0.35;
traversa.visible=STRAT_ON.poduri; traversa.position.set(pTinta.x,pTinta.y+.75,pTinta.z);
var pcte=[[tr,new THREE.Vector3(pTinta.x,pTinta.y+.75,pTinta.z)],
[new THREE.Vector3(pTinta.x-3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x-3.5,pTinta.y+.55,pTinta.z)],
[new THREE.Vector3(pTinta.x+3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x+3.5,pTinta.y+.55,pTinta.z)],
[tr,tr]];
for(var i=0;i<4;i++){
var g=cabluri[i].geometry;
g.setFromPoints(pcte[i]); cabluri[i].visible=STRAT_ON.poduri;
}
}
function ascundeCabluri(){cabluri.forEach(function(c){c.visible=false;});traversa.visible=false;}
var idxCur=0;
function aplica(dt){
var idx=-1;
for(var i=0;i<ETAPE.length;i++) if(timp>=praguri[i][0]&&timp<praguri[i][1]){idx=i;break;}
if(idx<0) idx=ETAPE.length-1;
idxCur=idx;
var e=ETAPE[idx], t=Math.min(Math.max((timp-praguri[idx][0])/e.t,0),1);
var P=caleP(e.c,t,e.arc);
grupProfil.position.copy(P);
traverse.visible = !MOD_TABLA && (idx>8 || (idx===8&&t>.5));
/* --- culoarea corpului --- */
var cul;
if(e.cul) cul=e.cul(t);
else if(idx<2) cul=CUL.rugina;
else if(idx<11) cul=CUL.sa3;
else cul=CUL.grund;
/* fara shop primer, otelul curatat se oxideaza inapoi intre sablare si vopsire */
if(RUST_BACK && idx>=4 && idx<12){
var kr=Math.min((idx-3)/6,1)*0.85;
cul=cul.clone().lerp(CUL_rustBack,kr);
}
matProfil.color.copy(cul); matProfil.emissive.copy(cul);
/* --- gaurile apar in timpul modulului de gaurire --- */
var gaurit = (idx>4) || (idx===4 && t>.45);
var sudat = (idx>8) || (idx===8 && t>.35);
/* barele paralele exista doar pana la intrarea in linia de gaurire (etapele 0-3) */
var inSablare = idx<=3;
PARALELE.forEach(function(g,k){
g.visible = (!MOD_TABLA) && inSablare && (N_PARALEL >= k+2);
});
if(MOD_TABLA){
profil.visible=false; gauri.visible=false;
traverse.visible=false; nervuri.visible=false; cordoane.visible=false;
tablaGrup.visible=true;
tablaGauri.visible=gaurit;
tablaNerv.visible=sudat; tablaCord.visible=sudat;
} else {
tablaGrup.visible=false;
profil.visible=true;
gauri.visible=gaurit;
traverse.visible=sudat; nervuri.visible=sudat; cordoane.visible=sudat;
}
/* --- culoarea cordoanelor: brute, apoi curatate, apoi grunduite --- */
if(idx<11) matCordon.color.copy(CUL.cordon);
else if(idx===11){
var kc=Math.min(Math.max((t-.35)/.4,0),1);
matCordon.color.copy(CUL.cordon.clone().lerp(CUL.sa3,kc));
} else if(idx===12){
var kg=Math.min(Math.max((t-.6)/.32,0),1);
var baza=RUST_BACK?CUL_rustBack:CUL.sa3;
matCordon.color.copy(baza.clone().lerp(CUL.grund,kg));
} else matCordon.color.copy(CUL.grund);
/* --- rotatorul de sudura --- */
rotator.visible=(idx===8);
if(idx===8){
rotator.position.copy(P); rotator.position.y=0;
var unghi=Math.sin(performance.now()/1000*.55)*1.15;
grupProfil.rotation.x=unghi;
} else if(idx>8&&idx<12){ grupProfil.rotation.x*=0.92; }
else grupProfil.rotation.x=0;
/* particule pe procese */
if(idx===2){var ot=OFU('tunel'); if(6.5+t*19.5>11&&6.5+t*19.5<21) emite(SC_SABL,poz(16.25+ot.L,AXA+ot.W,1.9),2.2,1.6);}
if(idx===4||idx===5) emite(SC_GAUR,P.clone().setY(P.y+.1),3.4,.5);
if(idx===8) emite(SC_SUD,P.clone().setY(P.y+.35),4.2,1.4);
var l=e.lum?e.lum(t):null;
if(l){lumProces.intensity=l.i;lumProces.color.setHex(l.c);lumProces.position.copy(l.p);}
else lumProces.intensity=0;
// poduri
var lw=caleLW(e.c,t);
ascundeCabluri();
var carE={L:8,W:8}, carV={L:45,W:38};
if(e.pod==='E'){carE={L:lw[0],W:lw[1]};}
else if(e.pod==='V'){carV={L:lw[0],W:lw[1]};}
else if(e.pod==='EV'){ if(lw[1]<20.5) carE={L:lw[0],W:lw[1]}; else carV={L:lw[0],W:lw[1]}; }
var pe=miscaPod(podE,carE.L,carE.W,true,dt||.016);
var pv=miscaPod(podV,carV.L,carV.W,true,dt||.016);
if(e.pod==='E') cabluriLa(podE,P);
if(e.pod==='V') cabluriLa(podV,P);
if(e.pod==='EV') cabluriLa(lw[1]<20.5?podE:podV,P);
// brat alimentare urmareste profilul pe linia de gaurire
var og=OFU('gaurire');
var Lc=Math.min(Math.max(lw[0]-6,33+og.L),59.5+og.L);
if(idx>=3&&idx<=6) carucior.position.copy(poz(Lc,6.5+og.W,1.0));
var TA=performance.now()/1000;
// turbine se invart cand tunelul lucreaza
var tunelActiv=(idx===2);
ANIM.turbine.forEach(function(d){ if(tunelActiv) d.rotation.y+=0.9; });
if(ANIM.perdea) ANIM.perdea.material.opacity=tunelActiv?(.10+Math.random()*.12):0;
// axele de gaurire lucreaza cand piesa e sub portal
var gaurire=(idx===4);
if(ANIM.axV){
ANIM.axV.position.y=ANIM.axVy+(gaurire?(-0.35+0.35*Math.abs(Math.sin(TA*3))):0);
var c1=Math.sin(TA*2.6), c2=Math.sin(TA*2.6+1.5);
ANIM.axH1.position.z=ANIM.axH1z+(gaurire? -0.3*Math.max(0,c1):0);
ANIM.axH2.position.z=ANIM.axH2z+(gaurire? 0.3*Math.max(0,c2):0);
if(gaurire){ANIM.axV.rotation.y+=1.1;}
}
// sudorii din zona 1 lucreaza in etapa de sudura
var sudActiv=(idx===8);
pist1.visible=sudActiv&&STRAT_ON.operatori; pist2.visible=sudActiv&&STRAT_ON.operatori;
if(sudActiv){
var s1=sudori[0][0], s2=sudori[0][1];
s1.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2);
s2.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2+1);
s1.tors.rotation.x=.18; s2.tors.rotation.x=.18;
var h1=new THREE.Vector3(); s1.bd.getWorldPosition(h1);
var h2=new THREE.Vector3(); s2.bd.getWorldPosition(h2);
pist1.position.set(h1.x+.15,h1.y-.1,h1.z-(0.35));
pist2.position.set(h2.x-.15,h2.y-.1,h2.z+(0.35));
pist1.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist2.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist1.userData.varf.material.transparent=true; pist2.userData.varf.material.transparent=true;
} else {
sudori[0][0].bd.rotation.x=0; sudori[0][1].bd.rotation.x=0;
sudori[0][0].tors.rotation.x=0; sudori[0][1].tors.rotation.x=0;
}
// sablatorul lucreaza cand piesa e in camera
var sabActiv=(idx===10&&t>.35);
jetSab.visible=sabActiv&&STRAT_ON.operatori; furtunSab.visible=(idx===10)&&STRAT_ON.operatori;
if(sabActiv){
var bal=Math.sin(TA*1.4)*2.6;
var ocs=OFU('camsab');
opCamSab.g.position.copy(poz(29+bal+ocs.L,27.7+ocs.W,0));
opCamSab.bd.rotation.x=-1.25;
jetSab.position.copy(poz(29+bal+ocs.L,27.0+ocs.W,1.35));
jetSab.rotation.x=Math.PI/2+.25;
jetSab.material.opacity=.18+Math.random()*.15;
furtunSab.position.copy(poz(29+bal+1.5+ocs.L,28.6+ocs.W,.8));
furtunSab.rotation.z=1.2; furtunSab.rotation.y=.4;
} else opCamSab.bd.rotation.x=0;
// vopsitorul lucreaza in etapa de vopsire
var vopsActiv=(idx===11&&t>.55);
jetVops.visible=vopsActiv&&STRAT_ON.operatori;
if(vopsActiv){
var bal2=Math.sin(TA*1.1)*3.0;
var ocv=OFU('camvops');
opVops.g.position.copy(poz(29+bal2+ocv.L,33.9+ocv.W,0));
opVops.bd.rotation.x=-1.2+0.15*Math.sin(TA*3);
jetVops.position.copy(poz(29+bal2+ocv.L,33.35+ocv.W,1.3));
jetVops.rotation.x=Math.PI/2+.2;
jetVops.material.opacity=.22+Math.random()*.14;
} else opVops.bd.rotation.x=0;
if(TIMP_REAL){
var scurs=0; for(var q=0;q<idx;q++) scurs+=ETAPE[q].min;
scurs+=ETAPE[idx].min*t;
document.getElementById('et-timp').textContent=
'minute '+Math.round(scurs)+' of '+T_REAL_TOTAL+' · stage lasts '+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?'Pause':'Play';
};
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:'Complete production line'},
{d:6, az:1.25, el:.62, r:95, tinta:[0,1,0], txt:'The building, 42.80 x 74.80 m'},
{d:7, az:2.05, el:.30, r:34, tinta:[-22,2,16], txt:'Raw material entry and the blasting tunnel'},
{d:7, az:0.55, el:.22, r:26, tinta:[10,2,16], txt:'The CNC drill line and bevel cutting'},
{d:6, az:-0.5, el:.34, r:38, tinta:[28,2,-8], txt:'Welding zones and fume extraction'},
{d:7, az:1.9, el:.26, r:30, tinta:[-8,2,-8], txt:'Blasting chambers and paint booths'},
{d:6, az:1.25, el:.85, r:78, tinta:[0,1,0], txt:'The complete flow, in a U shape'},
{d:5, az:1.55, el:.55, r:105,tinta:[0,1,0], txt:'The U-shaped flow, from the entry gate to the exit gate'}
];
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='Pause'; 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='Pause';
toast('Continuous flow: four parts on the line at the same time');
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('Real durations: 3 h 33 min per assembly. Welding takes 90 min of the 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('A 1,500 mm plate fills the whole tunnel window by itself. Switch to Profile for profiles in parallel.');
setTimeout(function(){toast(false);},4000);
return;
}
N_PARALEL = N_PARALEL>=3 ? 1 : N_PARALEL+1;
this.textContent = 'Blasting: '+N_PARALEL+(N_PARALEL===1?' profile':' profiles');
this.classList.toggle('activ', N_PARALEL>1);
if(N_PARALEL>1){
toast(N_PARALEL+' profiles in parallel through the tunnel. The 1,700 x 1,200 mm window takes them at the same time; blasting capacity multiplies, and the drill line then takes one profile at a time.');
} else {
toast('A single profile through the tunnel.');
}
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 ? 'Profile' : 'Plate';
if(MOD_TABLA){ N_PARALEL=1; btnPar.textContent='Blasting: 1 profile'; btnPar.classList.remove('activ'); }
if(MOD_TABLA){
toast('Plate: 12,000 x 1,500 mm. The same flow: blasting, cutting and drilling, welding, finishing.');
} else {
toast('Rolled profile: a 12 m beam, up to HEB / HEM 1000.');
}
setTimeout(function(){toast(false);},5000);
};
/* ============ INCARCAREA MODULELOR / CAPACITATE ============ */
var MODULE_CAP=[
{n:'Blasting in the tunnel', min:20},
{n:'Drilling and cutting',min:17},
{n:'Welding', min:90},
{n:'Blasting chamber', min:25},
{n:'Paint booth', 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/month';
}
document.getElementById('btn-200').onclick=function(){
SCENARIU = (SCENARIU===100)?200:100;
this.classList.toggle('activ',SCENARIU===200);
this.textContent = (SCENARIU===100)?'200 t/month test':'Back to 100 t/month';
document.getElementById('contor').style.display='block';
randeazaContor();
if(SCENARIU===200){
toast('If production doubles, welding becomes the bottleneck. The rest of the line has spare capacity.');
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='steel_structures_line_'+Date.now()+'.png'; a.click();
toast('Image saved to 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='Replay';}
}
actualizeazaPartic(SC_SUD,dt); actualizeazaPartic(SC_GAUR,dt); actualizeazaPartic(SC_SABL,dt);
/* pulsatie subtila lampi + plutire praf */
var tt=acum/1000;
if(ANIM.praf){
var a=ANIM.praf.geometry.attributes.position, b=ANIM.prafBase;
for(var pi=0;pi<a.count;pi++){
a.array[pi*3+1]=b[pi*3+1]+Math.sin(tt*.35+pi)*.28;
a.array[pi*3]=b[pi*3]+Math.sin(tt*.22+pi*.7)*.22;
}
a.needsUpdate=true;
}
aplica(dt); actualizeazaFantome(); pozCamera(); rend.render(scena,camera);
}
bucla();
/* ================= MODELE GLB REALE ================= */
function parseGLB(buf){
var dv=new DataView(buf), total=dv.getUint32(8,true), off=12, js=null, bin=null;
while(off<total){
var cl=dv.getUint32(off,true), ct=dv.getUint32(off+4,true);
if(ct===0x4E4F534A) js=JSON.parse(new TextDecoder().decode(new Uint8Array(buf,off+8,cl)));
else if(ct===0x004E4942) bin=buf.slice(off+8,off+8+cl);
off+=8+cl;
}
return {js:js,bin:bin};
}
function accData(js,bin,ai){
var a=js.accessors[ai], bv=js.bufferViews[a.bufferView];
var cs={5121:1,5123:2,5125:4,5126:4}[a.componentType];
var nc={SCALAR:1,VEC2:2,VEC3:3,VEC4:4}[a.type];
var stride=bv.byteStride||cs*nc, base=(bv.byteOffset||0)+(a.byteOffset||0);
var Arr={5121:Uint8Array,5123:Uint16Array,5125:Uint32Array,5126:Float32Array}[a.componentType];
var out=new Arr(a.count*nc), dvb=new DataView(bin);
var get={5126:function(o){return dvb.getFloat32(o,true)},5125:function(o){return dvb.getUint32(o,true)},
5123:function(o){return dvb.getUint16(o,true)},5121:function(o){return dvb.getUint8(o)}}[a.componentType];
for(var i=0;i<a.count;i++) for(var c=0;c<nc;c++) out[i*nc+c]=get(base+i*stride+c*cs);
return out;
}
function buildGLB(buf){
var P=parseGLB(buf), js=P.js, bin=P.bin;
var texCache={};
function tex(ti){
if(texCache[ti]) return texCache[ti];
var img=js.images[js.textures[ti].source], bv=js.bufferViews[img.bufferView];
var blob=new Blob([new Uint8Array(bin,bv.byteOffset||0,bv.byteLength)],{type:img.mimeType});
var t=new THREE.TextureLoader().load(URL.createObjectURL(blob));
t.flipY=false; t.wrapS=t.wrapT=THREE.RepeatWrapping;
texCache[ti]=t; return t;
}
var mats=(js.materials||[]).map(function(m){
var p=m.pbrMetallicRoughness||{};
var mm=new THREE.MeshStandardMaterial({
metalness:Math.min(p.metallicFactor!==undefined?p.metallicFactor:0.1,0.5),
roughness:p.roughnessFactor!==undefined?p.roughnessFactor:0.85,
side:m.doubleSided?THREE.DoubleSide:THREE.FrontSide});
if(p.baseColorFactor) mm.color.setRGB(p.baseColorFactor[0],p.baseColorFactor[1],p.baseColorFactor[2]);
if(p.baseColorTexture) mm.map=tex(p.baseColorTexture.index);
return mm;
});
var meshes=js.meshes.map(function(m){
var g=new THREE.Group();
m.primitives.forEach(function(pr){
var geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.BufferAttribute(accData(js,bin,pr.attributes.POSITION),3));
if(pr.attributes.NORMAL!==undefined) geo.setAttribute('normal',new THREE.BufferAttribute(accData(js,bin,pr.attributes.NORMAL),3));
if(pr.attributes.TEXCOORD_0!==undefined) geo.setAttribute('uv',new THREE.BufferAttribute(accData(js,bin,pr.attributes.TEXCOORD_0),2));
if(pr.indices!==undefined) geo.setIndex(new THREE.BufferAttribute(accData(js,bin,pr.indices),1));
if(pr.attributes.NORMAL===undefined) geo.computeVertexNormals();
var mesh=new THREE.Mesh(geo, pr.material!==undefined?mats[pr.material]:new THREE.MeshStandardMaterial());
mesh.castShadow=true; mesh.receiveShadow=true; g.add(mesh);
});
return g;
});
var nodes=js.nodes.map(function(n){
var g=new THREE.Group();
if(n.matrix){var mtx=new THREE.Matrix4().fromArray(n.matrix);mtx.decompose(g.position,g.quaternion,g.scale);}
else{
if(n.translation) g.position.fromArray(n.translation);
if(n.rotation) g.quaternion.fromArray(n.rotation);
if(n.scale) g.scale.fromArray(n.scale);
}
if(n.mesh!==undefined) g.add(meshes[n.mesh]);
return g;
});
js.nodes.forEach(function(n,i){(n.children||[]).forEach(function(c){nodes[i].add(nodes[c]);});});
var root=new THREE.Group();
js.scenes[js.scene||0].nodes.forEach(function(i){root.add(nodes[i]);});
return root;
}
function incarca(id){
return new Promise(function(res,rej){
try{
var b64=document.getElementById(id).textContent.replace(/\s/g,'');
var bin=atob(b64), n=bin.length, buf=new ArrayBuffer(n), v=new Uint8Array(buf);
for(var i=0;i<n;i++) v[i]=bin.charCodeAt(i);
res(buildGLB(buf));
}catch(e){rej(e);}
});
}
function aseaza(root,inaltime,peLatime){ /* normalizeaza: sta pe sol, centrat, la inaltimea data */
var w=new THREE.Group(); w.add(root);
var box=new THREE.Box3().setFromObject(root), size=new THREE.Vector3(); box.getSize(size);
var ref=peLatime?Math.max(size.x,size.z):size.y;
root.scale.setScalar(inaltime/ref);
box.setFromObject(root);
var c=new THREE.Vector3(); box.getCenter(c);
root.position.set(-c.x,-box.min.y,-c.z);
/* daca axul lung e pe X, roteste pe Z (deschiderea podului e pe Z) */
if(peLatime&&size.x>size.z) w.rotation.y=Math.PI/2;
return w;
}
new Promise(function(){}).then(function(model){
var s1=aseaza(model,1.78,false);
var s2=aseaza(buildGLB_cloneHack(model),1.78,false);
function pozSudor(w,L,W,rot){w.position.copy(poz(L,W,0));w.rotation.y+=rot;w.userData.strat='operatori';scena.add(w);inregistreazaStrat(w);if(typeof adaugaLaUnitate==='function')adaugaLaUnitate(w);}
pozSudor(s1,66.8,30.6,Math.PI*0.15);
pozSudor(s2,70.2,33.0,Math.PI*1.1);
sudori[0][0].g.visible=false; sudori[0][1].g.visible=false;
}).catch(function(e){console.warn('sudor glb:',e);});
function buildGLB_cloneHack(m){return m.clone(true);}
/* ================= STRATURI VIZIBILE ================= */
var STRATURI={hala:[],pereti:[],sablare:[],gaurire:[],sudura:[],camere:[],poduri:[],operatori:[],etichete:[]};
function inregistreazaStrat(o){
var st=o.userData&&o.userData.strat; if(!st||!STRATURI[st])return;
STRATURI[st].push(o); if(!STRAT_ON[st]) o.visible=false;
}
(function(){
[podE,podV,traversa].concat(cabluri).forEach(function(o){o.userData.strat='poduri';});
carucior.userData.strat='gaurire';
podea.userData.strat='hala';
[opSab,opGaur,opCamSab,opVops].forEach(function(o){o.g.userData.strat='operatori';});
sudori.forEach(function(pr){pr.forEach(function(o){o.g.userData.strat='operatori';});});
[pist1,pist2,jetSab,jetVops,furtunSab].forEach(function(o){o.userData.strat='operatori';});
if(ANIM.perdea) ANIM.perdea.userData.strat='sablare';
function autoStrat(o){
if(o.userData&&o.userData.strat) return o.userData.strat;
if(o.isSprite) return 'etichete';
if(o.isLight||o===grupProfil) return null;
if(o.isLine&&!o.userData.strat) return 'hala';
var L=o.position.x+HALA_L/2, W=HALA_W/2-o.position.z, y=o.position.y;
if(L<0.6) return 'hala';
if(y>7.2) return 'poduri';
var g=o.geometry&&o.geometry.parameters;
if(g&&g.height===H_PERETE) return 'pereti';
if(W<22) return (L<32)?'sablare':'gaurire';
return (L>=45)?'sudura':'camere';
}
grup.children.slice().forEach(function(o){
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
scena.children.slice().forEach(function(o){
if(o===grup||o===grupProfil||o.isLight) return;
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
document.querySelectorAll('#straturi input').forEach(function(cb){
cb.addEventListener('change',function(){
var st=cb.getAttribute('data-strat');
STRAT_ON[st]=cb.checked;
STRATURI[st].forEach(function(o){o.visible=cb.checked;});
});
});
})();
/* ============ COTARE VIE + DETECTIE COLIZIUNI ============ */
var COT_LIMITA=1.2; /* culoar minim admis intre echipamente, m */
var conturDrag=null, cotaSprite=[], conturCol=null;
function faceContur(culoare){
var g=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3(),
new THREE.Vector3(),new THREE.Vector3(),new THREE.Vector3()]);
var l=new THREE.Line(g,new THREE.LineBasicMaterial({color:culoare,linewidth:2}));
l.visible=false; scena.add(l); return l;
}
conturDrag=faceContur(0x2ECC71); conturCol=faceContur(0xE74C3C);
function spriteText(){
var c=document.createElement('canvas'); c.width=256; c.height=64;
var t=new THREE.CanvasTexture(c);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false}));
sp.scale.set(7,1.75,1); sp.visible=false; scena.add(sp);
sp.userData={cv:c,tx:t};
return sp;
}
for(var ci=0;ci<4;ci++) cotaSprite.push(spriteText());
function scrieSprite(sp,txt,culoare){
var c=sp.userData.cv, g=c.getContext('2d');
g.clearRect(0,0,256,64);
g.font='600 30px Segoe UI, sans-serif'; g.textAlign='center';
var w=g.measureText(txt).width+26;
g.fillStyle='rgba(16,12,18,.92)'; g.fillRect((256-w)/2,10,w,44);
g.fillStyle=culoare; g.fillText(txt,128,42);
sp.userData.tx.needsUpdate=true; sp.visible=true;
}
function limiteUnitate(u){
return {L1:u.b[0]+u.off.x, L2:u.b[1]+u.off.x, W1:u.b[2]-u.off.z, W2:u.b[3]-u.off.z};
}
function verificaColiziuni(u){
var a=limiteUnitate(u), rez={ok:true,cu:null};
if(a.L1<0.2||a.L2>74.6||a.W1<0.2||a.W2>42.6){rez.ok=false;rez.cu='building wall';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],'at south wall '+a.L1.toFixed(1)+' m',col);
cotaSprite[0].position.copy(poz(a.L1/2,(a.W1+a.W2)/2,1.4));
scrieSprite(cotaSprite[1],'at north wall '+(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],'at east wall '+a.W1.toFixed(1)+' m',col);
cotaSprite[2].position.copy(poz((a.L1+a.L2)/2,a.W1/2,1.4));
scrieSprite(cotaSprite[3],'at west wall '+(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? ('Moving: '+u.nume) : ('OVERLAPS with '+c.cu));
}
function ascundeCote(){
conturDrag.visible=false; conturCol.visible=false;
cotaSprite.forEach(function(sp){sp.visible=false;});
}
/* ============ TRASEE DE UTILITATI ============ */
function tub(p1,p2,r,culoare,strat){
var d=new THREE.Vector3().subVectors(p2,p1), len=d.length();
var m=new THREE.Mesh(new THREE.CylinderGeometry(r,r,len,8),
new THREE.MeshStandardMaterial({color:culoare,roughness:.55,metalness:.2,
emissive:new THREE.Color(culoare),emissiveIntensity:.3}));
m.position.copy(p1).addScaledVector(d,.5);
m.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.clone().normalize());
m.userData.strat=strat; m.visible=false; scena.add(m);
STRATURI[strat].push(m); return m;
}
STRATURI.aer=[]; STRATURI.electric=[]; STRATURI.aspiratie=[];
STRAT_ON.aer=false; STRAT_ON.electric=false; STRAT_ON.aspiratie=false;
/* aer comprimat - albastru, pe peretele est, coborari la camera de sablare si la posturi */
(function(){
var y=6.2;
tub(poz(2,1.2,y),poz(72,1.2,y),.13,0x2E86DE,'aer');
[16,48,68].forEach(function(L){ tub(poz(L,1.2,y),poz(L,1.2,1.6),.10,0x2E86DE,'aer'); });
tub(poz(29,1.2,y),poz(29,26.3,y),.12,0x2E86DE,'aer');
tub(poz(29,26.3,y),poz(29,26.3,2.2),.10,0x2E86DE,'aer');
})();
/* alimentare electrica - galben, jgheab principal + coborari la tablouri */
(function(){
var y=7.0;
tub(poz(4,21.5,y),poz(72,21.5,y),.16,0xF1C40F,'electric');
[[16,4],[48,4],[29,26.3],[29,32.8],[60,31.8]].forEach(function(p){
tub(poz(p[0],21.5,y),poz(p[0],p[1],y),.11,0xF1C40F,'electric');
tub(poz(p[0],p[1],y),poz(p[0],p[1],2.0),.09,0xF1C40F,'electric');
});
})();
/* aspiratie - gri, de la zonele de sudura si camere spre exterior */
(function(){
var y=5.6;
tub(poz(52,40.5,y),poz(70,40.5,y),.3,0x95A5A6,'aspiratie');
[52.5,60.5,68.5].forEach(function(L){ tub(poz(L,40.5,y),poz(L,34,y),.22,0x95A5A6,'aspiratie'); });
tub(poz(29,26.3,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,32.8,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,22,5.6),poz(70,22,5.6),.3,0x95A5A6,'aspiratie');
})();
/* ================= MUTARE ECHIPAMENTE (drag & drop) ================= */
var MUTARE=false, dragUnit=null;
var UNITATI=[
{id:'tunel', nume:'Blasting line', b:[0.3,32.4, 0.6,11.2]},
{id:'rastel', nume:'Raw material storage area', b:[0.3,12.7, 9.6,16.0]},
{id:'gaurire',nume:'Drilling + cutting line',b:[31.6,73.8, 0.6,9.0]},
{id:'sud1', nume:'Welding zone 1', b:[64.4,72.6,22.2,42.6]},
{id:'sud2', nume:'Welding zone 2', b:[56.4,64.4,22.2,42.6]},
{id:'sud3', nume:'Welding zone 3', b:[48.4,56.4,22.2,42.6]},
{id:'camsab', nume:'Blasting chamber', b:[21.7,38.0,23.2,29.6]},
{id:'camvops',nume:'Paint booth', b:[21.7,38.0,29.7,36.2]},
{id:'depozit',nume:'Finished product storage', 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='Play';
toast('Drag a machine to move it. The background rotates the 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 saved to 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 loaded: '+(date.data||f.name)); setTimeout(function(){toast(false);},2500);
}catch(e){toast('Invalid layout file'); 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>
