Reference configuration
Complete laser line for profiles and plate
From a 12 m raw profile to the painted structure in a single flow. A 20 kW fibre-laser machine replaces mechanical drilling and the band saw, and also cuts plate. A reference configuration sized from 100 tonnes a month.
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 the laser changes
This variant starts from the same building and the same tonnage as the line with CNC drilling and a band saw, but changes the heart of the line: one fibre-laser machine cuts the profiles on all faces, makes the bevel and also cuts the plate for gussets, base plates and stiffeners. A new stage is added too, local blasting of the welds before painting, with a vacuum unit, no dust and no booth.
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. Large profiles pass lying down.
- 6 turbines × 11 kW, steel shot and grit abrasive in a closed circuit
- Working window 2,000 × 600 mm; profiles and plate, parts up to 2,000 × 500 × 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


2Laser cutting for profiles and plate
One machine, in line with the tunnel, replaces mechanical drilling and the band saw. Holes, cut-outs, marks, the bevel and the cut to length are done in a single clamping, straight from the 3D model.
- 20 kW fibre laser, tilting cutting head (3 in 1: profiles, plate, bevel)
- H, I, U, L profiles from 200 × 100 up to 1,500 × 500 mm, up to 12.5 m long; covers HEB / HEM 1000
- Plate on two stations of 13 × 3.5 m (12 × 3 m when bevelling), with the same source
- Bevel at 90° and ±45°, positioning accuracy ±0.1 mm/m, repeatability ±0.1 mm
- The profile is scanned on loading and the program is corrected automatically for its real shape; collision protection
- Nesting for profiles and plate; DXF, DWG and 3D model import, Tekla compatible
- About 36 × 6 m, in line with the tunnel; power about 110 kW

3–6Two assembly and welding zones
Each zone has two stations. Welding still sets the pace of the line: 90 minutes of the 198-minute cycle. Zone 1 works on rotators, zone 2 on a 6 m welding table, and zone 3 is reserved.
- Four pulsed MIG/MAG machines of 400 A, two per zone
- Zone 1: chain rotators for 12 m profiles, with no crane
- Zone 2: a modular 6 × 1.5 m table, from four modules with a 25 mm top and a grid of 28 mm holes
- Fume filtration: 2 compact units × 12,000 m³/h, recirculating into the shop (about 4.6 air changes/h); extendable to 3 or 5 units
- Standard: spark detection and a frequency converter



7–9Weld blasting and the paint booth
After assembly, the welds are blasted locally, in zone 3, with a vacuum unit, in a closed circuit, with no dust and no booth. The assembly then goes into the paint booth, where the paint is applied with an airless pump.
- Local vacuum blasting to Sa 2½ (ISO 8501-1), 6 – 15 m²/h, steel shot abrasive 0.8 – 1.0 mm; the dust is collected in bags
- Booth of 12.8 × 3.9 × 3.9 m, a motorised gate of 3.7 × 3.7 m and two service doors
- Longitudinal airflow of 0.5 m/s, about 30,000 m³/h; forced drying at 40 – 60 °C
- 330 kW electric heating in 6 steps, with a rotary heat recovery unit (over 50% efficiency), no gas connection
- Pneumatic airless pump for high-solids anticorrosion paints; two 3 t technological trolleys for the parts
This is a reference configuration: the parameters are confirmed on each customer’s project, before a quotation.
Step by step: what happens to a 12 m profile
The 12 stages below follow the cycle in the technical file. Durations are calculated on a feed rate of 1 m/min in the tunnel. In the 3D simulation, laser cutting appears in two stages (holes and cut-outs, then bevel).
The critical operation is welding: 90 minutes of 198, or 45% of the cycle. That is why there are two assembly zones with two stations each, and a third zone kept in reserve. 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 stations and zones, not a second tunnel.
How it is laid out in the building
The flow closes in a U shape. The tunnel and the laser machine sit in line, along the long wall, and leave a free central aisle of 11.5 m by the tunnel and about 8 m by the laser machine.

- The blasting tunnel needs 31.5 m of continuous length; the 21 m bays, with no intermediate columns, meet the condition
- The laser machine needs 36 m of length and 6 m of width, in line with the tunnel: the series line is 67.5 m long, in a 74.8 m building
- A free aisle of 13 m must remain in front of the paint booth gate, for 12 m parts
- The clear height under the beam must allow for the 4 m height of the paint booth 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 770 kW, of which 330 kW is the electric heating bank of the paint booth; we recommend a 1,000 kVA transformer station. The grid connection procedure is the longest lead time in the project, so it is started early.
- Compressed air: about 7.5 m³/min at 8 bar (local vacuum blasting about 6.1 m³/min, the airless pump about 1.5 m³/min; blasting and painting do not run at the same time), with a refrigerant dryer, a buffer tank and the distribution network. The compressor (about 45 kW) can be bought or rented, but it must be available at commissioning.
- Gases for the laser: oxygen or nitrogen (cylinders or a tank); dry compressed air serves the drives and, optionally, as a cutting gas.
- Overhead crane: for the 12 m profiles and for the assemblies, along the whole length of the building, including loading the two stations of the laser machine.
- Civil works: a flat concrete platform of 36 × 6 m for the laser machine, 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 the equipment of the line, the handling between machines (storage area, cross transfers, roller tables), transport, installation, commissioning, training (4 days for the laser machine: programming, operation, maintenance) 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 laser machine is delivered in a minimum of 150 days from a firm order, in step with the progress of the building.
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 laser line - 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 laser line for profiles and plate</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>Laser cutting<br>
<span class="cub" style="background:#D8C271"></span>Welding zones<br>
<span class="cub" style="background:#A98BC0"></span>Paint booth<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>Laser 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 18 min</b>
</div>
</div>
<div class="panou" id="toast" style="display:none;top:112px;right:16px;font-size:13px"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
var HALA_L=74.8, HALA_W=42.8, H_PERETE=10, AXA=3.84;
var scena=new THREE.Scene();
scena.background=new THREE.Color(0x151016);
scena.fog=new THREE.Fog(0x151016,130,280);
var camera=new THREE.PerspectiveCamera(48,innerWidth/innerHeight,.1,600);
var rend=new THREE.WebGLRenderer({antialias:true,alpha:false});
rend.setSize(innerWidth,innerHeight);
rend.setPixelRatio(Math.min(devicePixelRatio,2));
rend.shadowMap.enabled=true; rend.shadowMap.type=THREE.PCFSoftShadowMap;
rend.toneMapping=THREE.ACESFilmicToneMapping; rend.toneMappingExposure=0.95;
rend.outputEncoding=THREE.sRGBEncoding;
document.getElementById('scena').appendChild(rend.domElement);
scena.add(new THREE.AmbientLight(0xffffff,.42));
var soare=new THREE.DirectionalLight(0xfff6ea,1.6);
soare.position.set(50,70,40); soare.castShadow=true;
soare.shadow.camera.left=-75; soare.shadow.camera.right=75;
soare.shadow.camera.top=75; soare.shadow.camera.bottom=-75;
soare.shadow.mapSize.set(2048,2048); scena.add(soare);
var umplere=new THREE.DirectionalLight(0x9FB4FF,.3);
umplere.position.set(-40,50,-40); scena.add(umplere);
/* ORIENTARE: X = L (portile la stanga), Z = est spre privitor (linia jos) */
function poz(L,W,y){return new THREE.Vector3(L-HALA_L/2, y||0, HALA_W/2-W);}
var grup=new THREE.Group(); scena.add(grup);
var ANIM={oameni:[]};
var STRAT_ON={hala:true,pereti:true,sablare:true,gaurire:true,sudura:true,camere:true,poduri:true,operatori:true,etichete:true};
function mat(c,opt){var o=opt||{};
var m=new THREE.MeshStandardMaterial({color:c,roughness:o.r!==undefined?o.r:.6,
metalness:o.m||0,transparent:o.o!==undefined,opacity:o.o!==undefined?o.o:1});
/* utilajele primesc o emisie proprie discreta ca sa nu se piarda pe fundal inchis */
if(!(o.r>=.9)){ m.emissive=new THREE.Color(c); m.emissiveIntensity=0.22; }
return m;}
function cutie(L,W,y,dl,dw,h,material,umbre){
var ms=new THREE.Mesh(new THREE.BoxGeometry(dl,h,dw),material);
ms.position.copy(poz(L,W,y)); if(umbre!==false){ms.castShadow=true;ms.receiveShadow=true;}
grup.add(ms); return ms;
}
/* ---------- hala ---------- */
var podea=cutie(HALA_L/2,HALA_W/2,-0.15,HALA_L+0.8,HALA_W+0.8,0.3,mat(0x1C1A24,{r:1.0}));
podea.castShadow=false;
var gmat=new THREE.LineBasicMaterial({color:0x3A3550});
for(var l=0;l<=HALA_L+.1;l+=10.6){
var g=new THREE.BufferGeometry().setFromPoints([poz(l,0,.01),poz(l,HALA_W,.01)]);
grup.add(new THREE.Line(g,gmat));
}
var matPerete=new THREE.MeshStandardMaterial({color:0x6B6480,roughness:.9,
side:THREE.BackSide,transparent:true,opacity:0.16,depthWrite:false});
function zid(L,W,dl,dw,h,y){
var m=cutie(L,W,(y||0)+h/2,dl,dw,h,matPerete,false);
m.userData.strat='pereti';
/* muchie subtire, ca hala sa ramana citibila si cand peretii sunt stinsi */
var ed=new THREE.LineSegments(new THREE.EdgesGeometry(m.geometry),
new THREE.LineBasicMaterial({color:0x8A83A8,transparent:true,opacity:.5}));
ed.position.copy(m.position); ed.userData.strat='pereti'; grup.add(ed);
return m;}
zid(HALA_L/2,0,HALA_L,.3,H_PERETE); // est
zid(HALA_L/2,HALA_W,HALA_L,.3,H_PERETE); // vest
zid(HALA_L,HALA_W/2,.3,HALA_W,H_PERETE); // nord
zid(0,5.475,.3,10.95,H_PERETE); // sud, intre porti
zid(0,21.4,.3,10.9,H_PERETE);
zid(0,37.325,.3,10.95,H_PERETE);
zid(0,13.45,.3,5,H_PERETE-5.5,5.5);
zid(0,29.35,.3,5,H_PERETE-5.5,5.5);
function poarta(W,c){
var m=cutie(0,W,2.75,.35,5,5.5,mat(c,{o:.5}));
m.material.emissive=new THREE.Color(c); m.material.emissiveIntensity=.25;
}
poarta(13.45,0x2E9E5B); poarta(29.35,0x3B6E8F);
for(var l2=.92;l2<HALA_L;l2+=10.6) cutie(l2,21.56,H_PERETE/2,.8,.6,H_PERETE,mat(0x59505F,{r:.7}));
/* ================= ATMOSFERA ================= */
scena.background=new THREE.Color(0x16141E);
scena.fog=null;
/* lumina de umplere calda dinspre podea (bounce) */
var bounce=new THREE.HemisphereLight(0xCFE0FF,0x3B3746,.4); scena.add(bounce);
/* corpuri de iluminat industriale suspendate */
var LAMPI=[];
for(var li=8;li<HALA_L;li+=13){
for(var lw=9;lw<HALA_W;lw+=12){
var cupa=new THREE.Mesh(new THREE.ConeGeometry(.62,.5,14,1,true),
new THREE.MeshStandardMaterial({color:0x4A4350,side:THREE.DoubleSide,roughness:.6}));
cupa.position.copy(poz(li,lw,8.4)); grup.add(cupa);
var bec=new THREE.Mesh(new THREE.SphereGeometry(.2,10,10),
new THREE.MeshBasicMaterial({color:0xFFF3DC}));
bec.position.copy(poz(li,lw,8.15)); grup.add(bec); LAMPI.push(bec);
var tija=new THREE.Mesh(new THREE.CylinderGeometry(.03,.03,1.4,6),mat(0x3A3440));
tija.position.copy(poz(li,lw,9.3)); grup.add(tija);
var pl=new THREE.PointLight(0xFFE9C4,.42,26,2);
pl.position.copy(poz(li,lw,8.0)); scena.add(pl);
}
}
/* raze de lumina prin luminatoarele de acoperis */
var RAZE=[];
for(var ri=12;ri<HALA_L-6;ri+=16){
var raza=new THREE.Mesh(new THREE.CylinderGeometry(2.6,5.2,H_PERETE+2,16,1,true),
new THREE.MeshBasicMaterial({color:0xFFE7BC,transparent:true,opacity:.055,
side:THREE.DoubleSide,depthWrite:false,blending:THREE.AdditiveBlending}));
raza.visible=false;
raza.position.copy(poz(ri,HALA_W*0.42,(H_PERETE+2)/2)); raza.rotation.z=.10;
scena.add(raza); RAZE.push(raza);
}
/* praf in suspensie, prins de lumina */
(function(){
var n=900, g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
for(var i=0;i<n;i++){
var q=poz(Math.random()*HALA_L,Math.random()*HALA_W,.4+Math.random()*7.5);
p[i*3]=q.x; p[i*3+1]=q.y; p[i*3+2]=q.z;
}
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var praf=new THREE.Points(g,new THREE.PointsMaterial({color:0xFFE2B0,size:.075,
transparent:true,opacity:.28,depthWrite:false,blending:THREE.AdditiveBlending}));
scena.add(praf); ANIM.praf=praf; ANIM.prafBase=p.slice();
})();
/* ---------- etichete ---------- */
function etich(txt,L,W,y,scara){
var c=document.createElement('canvas');c.width=512;c.height=110;
var g=c.getContext('2d');
g.font='600 46px Segoe UI, sans-serif';g.textAlign='center';
var w=g.measureText(txt).width+44;
g.fillStyle='rgba(16,12,18,.88)';
g.beginPath();g.rect((512-w)/2,12,w,80);g.fill();
g.fillStyle='#EAE4DC';g.fillText(txt,256,68);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:new THREE.CanvasTexture(c),depthTest:false}));
var s=(scara||11)*((innerWidth<900)?0.78:0.82); sp.scale.set(s,s*110/512,1);
sp.position.copy(poz(L,W,y)); grup.add(sp);
}
/* ---------- role ---------- */
var matRola=mat(0xB0342E,{m:.35,r:.45}), matCadru=mat(0x2E5C8A,{r:.6}), matSina=mat(0xD8C231,{r:.55});
function role(L1,L2,W){
cutie((L1+L2)/2,W,.45,L2-L1,2,.55,matCadru);
cutie((L1+L2)/2,W-0.97,.76,L2-L1,.12,.1,matSina);
cutie((L1+L2)/2,W+0.97,.76,L2-L1,.12,.1,matSina);
for(var l=L1+.4;l<L2;l+=.8){
var r=new THREE.Mesh(new THREE.CylinderGeometry(.11,.11,1.8,10),matRola);
r.rotation.x=Math.PI/2; r.position.copy(poz(l,W,.86)); grup.add(r);
}
for(var l3=L1+2;l3<L2;l3+=4) cutie(l3,W+1.25,.5,.5,.4,.4,matCadru); // motoare
}
role(0.5,12.5,AXA); role(20,32,AXA); role(60.5,68,AXA); /* v3: laser 33-60 m, role de descarcare 60,5-68 m */ /* masa de intrare gaurire 8 m (rev. 14.09) */
/* ---------- TUNEL DE SABLARE (reconstruit dupa referinta) ---------- */
(function(){
var gri=mat(0x9AA1AA,{r:.6}), inchis=mat(0x3A3F46,{r:.55}), galben=mat(0xE0C231,{r:.55});
/* soclu de beton */
cutie(16.2,AXA,.05,8.6,5.4,.1,mat(0xB5B0A8,{r:.95}),false);
/* vestibule de etansare */
cutie(13.2,AXA,1.65,1.5,3.2,2.9,gri);
cutie(19.1,AXA,1.65,1.5,3.2,2.9,gri);
/* camera de sablare - corp inchis la culoare, ca in referinta */
cutie(16.15,AXA,2.4,4.2,3.7,4.4,inchis);
cutie(16.15,AXA,4.62,4.4,3.9,.14,gri,false);
/* dunga rosie la nivelul rolelor */
cutie(16.15,2.0,0.9,4.2,.05,.12,mat(0xB0342E));
/* 6 turbine cu motoare si discuri rotative (aceleasi pozitii) */
var matTurb=mat(0x565E68,{r:.5}), matMotor=mat(0x3E4650,{m:.3,r:.4});
var pozT=[[15.1,1.35,4.0,.5],[16.25,1.35,3.2,-.4],[17.4,1.35,4.0,.5],
[15.1,6.33,4.0,-.5],[16.25,6.33,3.2,.4],[17.4,6.33,4.0,-.5]];
ANIM.turbine=[];
for(var i=0;i<6;i++){
var p=pozT[i];
var t=new THREE.Mesh(new THREE.BoxGeometry(1.0,1.0,1.0),matTurb);
t.position.copy(poz(p[0],p[1],p[2])); t.rotation.x=p[3]; t.castShadow=true; grup.add(t);
var dirW=(p[1]<AXA)?-1:1;
var mo=new THREE.Mesh(new THREE.CylinderGeometry(.27,.27,.9,12),matMotor);
mo.position.copy(poz(p[0],p[1]+dirW*0.95,p[2])); mo.rotation.x=Math.PI/2; mo.castShadow=true; grup.add(mo);
var disc=new THREE.Mesh(new THREE.CylinderGeometry(.34,.34,.09,16),galben);
disc.rotation.x=Math.PI/2; disc.position.copy(poz(p[0],p[1]+dirW*1.45,p[2])); grup.add(disc);
ANIM.turbine.push(disc);
}
/* buncar si separator DEASUPRA camerei, pe platforma cu balustrada galbena */
var buncar=new THREE.Mesh(new THREE.CylinderGeometry(1.5,.5,1.3,4),gri);
buncar.rotation.y=Math.PI/4; buncar.position.copy(poz(16.15,AXA,5.5)); grup.add(buncar);
cutie(16.15,AXA,6.7,2.8,1.9,1.2,gri);
cutie(16.15,AXA,6.12,4.6,3.4,.14,mat(0x556070),false);
[2.2,5.5].forEach(function(w){
for(var b=0;b<5;b++) cutie(14.3+b*1.1,w,6.7,.06,.06,1.05,galben);
cutie(16.35,w,7.25,4.4,.06,.08,galben);
});
/* turn elevator cu ferestre de vizitare rosii */
cutie(16.15,6.9,3.9,1.1,.95,7.8,gri);
for(var e=1.2;e<7;e+=1.1) cutie(16.15,6.42,e,.6,.05,.5,mat(0xA8313A));
cutie(16.15,6.9,8.0,1.5,1.3,.9,gri);
/* ciclon + racord spre desprafuitor */
var cic=new THREE.Mesh(new THREE.CylinderGeometry(.6,.22,1.9,12),gri);
cic.position.copy(poz(13.9,7.7,5.2)); grup.add(cic);
/* dulap electric mare, pe pardoseala */
cutie(11.2,8.9,1.15,1.7,1.1,2.3,mat(0x565E68));
etich('BLASTING TUNNEL',16.15,AXA,9.3,11);
etich('6 x TURBINES 11 kW',16.25,8.6,8.9,9);
})();
/* ---------- MASINA DE DEBITARE LASER (profile + tabla, doua statii) ---------- */
(function(){
var alb=mat(0xE8ECEF,{r:.55}), albastru=mat(0x2F4F8F,{r:.55}), inchis=mat(0x3A3F46,{r:.55}), verde=mat(0x7BA88A,{r:.55});
// batiu: doua statii de 13 m (33-46 si 47-60), latime 6 m centrata pe AXA
cutie(39.5,AXA,.45,13,5.6,.9,albastru); // statia 1
cutie(53.5,AXA,.45,13,5.6,.9,albastru); // statia 2
// gratare de taiere (lamele) pe fiecare statie
for(var i=0;i<26;i++){ cutie(33.4+i*.5,AXA,.95,.08,5.2,.18,inchis); cutie(47.4+i*.5,AXA,.95,.08,5.2,.18,inchis); }
// sine longitudinale ale portalului, 33-60 m
cutie(46.5,AXA-3.1,.5,27,.3,.35,inchis); cutie(46.5,AXA+3.1,.5,27,.3,.35,inchis);
// protectii laterale din tabla (pereti scunzi) pe toata lungimea
cutie(46.5,AXA-3.25,1.35,27,.1,1.5,alb,false); cutie(46.5,AXA+3.25,1.35,27,.1,1.5,alb,false);
// portal (gantry) la 48.5 m: doua coloane + traversa, cap laser
cutie(48.5,AXA-2.9,1.7,1.4,.6,3.4,alb);
cutie(48.5,AXA+2.9,1.7,1.4,.6,3.4,alb);
cutie(48.5,AXA,3.55,1.2,6.4,.7,alb); // traversa
cutie(48.5,AXA,3.0,.9,.9,.6,inchis); // carucior Y
var axV=new THREE.Mesh(new THREE.CylinderGeometry(.14,.14,1.2,12),mat(0x2F4F8F,{m:.5}));
axV.position.copy(poz(48.5,AXA,2.2)); grup.add(axV); // cap laser (se misca pe verticala)
var axH1=new THREE.Mesh(new THREE.CylinderGeometry(.01,.01,.01,4),inchis); axH1.position.copy(poz(48.5,AXA,1.2)); grup.add(axH1);
var axH2=axH1.clone(); grup.add(axH2);
ANIM.axV=axV; ANIM.axH1=axH1; ANIM.axH2=axH2;
ANIM.axVy=axV.position.y; ANIM.axH1z=axH1.position.z; ANIM.axH2z=axH2.position.z;
// sursa laser + chiller + dulap comanda, pe latura de vest a masinii
cutie(44,8.2,.9,2.2,1.0,1.8,mat(0x5E6670)); // sursa laser 20 kW
cutie(46.6,8.2,.8,1.6,1.0,1.6,mat(0x5E6670)); // chiller
cutie(50,8.2,1.0,1.4,1.0,2.0,mat(0x5E6670)); // dulap + consola operator
cutie(62,8.0,1.2,.8,.8,2.4,mat(0x39424E)); // exhaustare
etich('LASER CUTTING',46.5,AXA,7.2,12);
})();
/* brat de alimentare (carucior cu clema) - pozitionarea profilului pe statie */
var carucior=new THREE.Group();
carucior.add(new THREE.Mesh(new THREE.BoxGeometry(1.2,.7,.8),mat(0x4F7861)));
var clema=new THREE.Mesh(new THREE.BoxGeometry(.25,.5,1.2),mat(0x395143));
clema.position.set(.4,.5,-.7); carucior.add(clema);
carucior.position.copy(poz(33,6.5,1.0)); scena.add(carucior);
cutie(46.5,6.5,.35,27,.35,.25,mat(0x39424E)); // sina carucior
/* ---------- SPATIU DE DEPOZITARE MATERIAL BRUT ---------- */
(function(){
for(var i=0;i<4;i++){
var a=new THREE.Mesh(new THREE.BoxGeometry(.25,1.6,5),mat(0x8E99A6));
a.position.copy(poz(2+i*3,12.8,.8)); grup.add(a);
}
var matBara=mat(0x8A5A3C,{r:.7,m:.3});
for(var j=0;j<3;j++){
var b=new THREE.Mesh(new THREE.BoxGeometry(12,.4,.35),matBara);
b.position.copy(poz(6.5,11.6+j*1.1,1.15)); b.castShadow=true; grup.add(b);
}
etich('STORAGE AREA',6.5,12.8,3.4,10);
})();
/* ---------- ZONE SUDURA (v3: zona 1 rotatoare, zona 2 masa 6 m, zona 3 ulterior) ---------- */
(function(){
var galben=mat(0xD8C271,{o:.85}), galbenSlab=mat(0xD8C271,{o:.28});
var gri=mat(0x50565E,{m:.3}), inchis=mat(0x3A3F46), rosu=mat(0x8A2F2F), otel=mat(0x5E7889);
function aparat(L,W){ // sursa MIG/MAG pe carucior + butelie
cutie(L,W,.55,.7,.5,1.0,rosu);
var but=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,1.1,10),otel);
but.position.copy(poz(L+.5,W-.3,.6)); grup.add(but);
}
// zona 1 (65-72 m): doua posturi pe rotatoare cu lant
cutie(68.5,31.8,.12,7,18,.24,galben,false);
[66.3,70.7].forEach(function(L){
cutie(L,24.5,.5,1.4,1.2,1.0,mat(0x4F7861));
var lant=new THREE.Mesh(new THREE.TorusGeometry(.55,.09,8,20),mat(0x39424E,{m:.4}));
lant.position.copy(poz(L,24.5,1.35)); grup.add(lant);
});
aparat(65.9,28.6); aparat(71.1,35.0);
etich('WELDING 1 - ROTATORS',68.5,31.8,4.6,9);
// zona 2 (57-64 m): doua posturi pe masa de sudura modulara 6 x 1,5 m
cutie(60.5,31.8,.12,7,18,.24,galben,false);
cutie(60.5,31.8,.9,6,1.5,.14,gri); // blat 25 mm, 4 module
for(var m=1;m<4;m++) cutie(57.5+m*1.5,31.8,.98,.03,1.5,.02,inchis); // rosturile modulelor
[[-2.7,-.55],[-2.7,.55],[-.9,-.55],[-.9,.55],[.9,-.55],[.9,.55],[2.7,-.55],[2.7,.55]].forEach(function(p){
cutie(60.5+p[0],31.8+p[1],.42,.12,.12,.84,inchis);
});
aparat(57.9,28.6); aparat(63.1,35.0);
etich('WELDING 2 - 6 m TABLE',60.5,31.8,4.6,9);
// zona 3 (49-56 m): rezervata, se echipeaza ulterior
cutie(52.5,31.8,.12,7,18,.24,galbenSlab,false);
etich('ZONE 3 - WELD BLASTING',52.5,31.8,4.6,9);
cutie(50.2,26.8,.08,.9,.9,.12,mat(0x3A3F46));
cutie(50.2,26.8,.85,1.8,.8,1.7,mat(0x2F4F8F,{m:.3})); /* unitate de sablare cu vid AT200 */
cutie(50.2,27.6,1.9,.5,.5,.5,mat(0x8A939E)); /* siloz / separator */
// doua unitati compacte de filtrare (varianta A), cate una pe zona activa
[68.5,60.5].forEach(function(L){
cutie(L,41.6,1.4,1.6,1.6,2.8,mat(0x2F4F8F,{r:.5})); // corpul inferior (filtru)
cutie(L,41.6,3.5,1.5,1.5,1.3,mat(0xE8ECEF,{r:.55})); // corpul superior cu duze
for(var k=0;k<3;k++){ var dz=new THREE.Mesh(new THREE.CylinderGeometry(.16,.16,.5,10),mat(0x2F4F8F)); dz.rotation.x=Math.PI/2; dz.position.copy(poz(L-.5+k*.5,40.7,3.7)); grup.add(dz); }
});
etich('FUME FILTERS 2 x 12,000',64.5,41.6,5.4,9);
})();
/* ---------- CAMERE (sablare + vopsire) ---------- */
function camba(L1,L2,W1,W2,nume,vopsire){
var alb=mat(0xF3F2F4,{o:.38,r:.7}), rosu=mat(0xA8313A,{r:.55}), albastru=mat(0x3B6EC0,{r:.5});
var h=4, Lm=(L1+L2)/2, Wm=(W1+W2)/2;
cutie(Lm,W1,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,W2,h/2,L2-L1,.16,h,alb,false);
cutie(Lm,Wm,h+.05,L2-L1,W2-W1,.14,mat(0xF3F2F4,{o:.55}),false);
for(var l=L1;l<=L2+.01;l+=2.17){
cutie(l,W1,h/2,.2,.24,h,rosu); cutie(l,W2,h/2,.2,.24,h,rosu);
}
[0.12,h-0.12].forEach(function(y){
cutie(Lm,W1,y,L2-L1,.26,.2,rosu,false); cutie(Lm,W2,y,L2-L1,.26,.2,rosu,false);
});
[[L1,-1],[L2,1]].forEach(function(cap){
cutie(cap[0],W1+.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=cap[1]*0.5;
cutie(cap[0],W2-.9,h/2-.25,.14,1.6,h-.5,rosu,false).rotation.y=-cap[1]*0.5;
cutie(cap[0],Wm,h-.25,.2,W2-W1,.5,rosu,false);
});
if(vopsire){
cutie(L1+2,Wm,h+.65,2.2,1.6,1.2,mat(0x7E8794));
var tOr=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,(L2-L1)*.55,12),albastru);
tOr.rotation.z=Math.PI/2; tOr.position.copy(poz(Lm-1,Wm,h+.75)); grup.add(tOr);
var tVe=new THREE.Mesh(new THREE.CylinderGeometry(.45,.45,2.6,12),albastru);
tVe.position.copy(poz(L2-1.5,Wm,h+1.9)); grup.add(tVe);
}else{
cutie(Lm,W1-0.9,1.4,3.2,1.4,2.8,mat(0x7E8794));
var cos2=new THREE.Mesh(new THREE.CylinderGeometry(.2,.2,2.4,10),mat(0x8B93A0));
cos2.position.copy(poz(Lm,W1-0.9,3.9)); grup.add(cos2);
cutie(Lm,Wm,.06,L2-L1-1,1.1,.12,mat(0x3E4650));
var tOr2=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,3.4,12),albastru);
tOr2.rotation.x=Math.PI/2; tOr2.position.copy(poz(Lm,W1+0.8,h+.7)); grup.add(tOr2);
}
etich(nume,Lm,Wm,5.6,11);
}
camba(22.5,35.5,30.8,34.8,'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]].forEach(function(z){
var mE=mat(0x7A2E2E,{o:.45,r:.85});
cutie(z[0],29.1,1.1,5.0,.08,2.2,mE,false);
cutie(z[0],34.5,1.1,5.0,.08,2.2,mE,false);
cutie(z[0]+2.6,31.8,1.1,.08,5.5,2.2,mE,false);
});
/* rastel butelii linga zona 3 */
cutie(46.6,29.5,.75,1.6,.7,1.5,mat(0x4A5560));
for(var bb=0;bb<3;bb++){
var bt=new THREE.Mesh(new THREE.CylinderGeometry(.15,.15,1.3,10),mat(0x5E7889));
bt.position.copy(poz(46.2+bb*.45,29.5,.75)); grup.add(bt);
}
/* camera sablare: unitate de recuperare RU + benzi de lumina */
cutie(36.6,25.0,1.15,1.5,1.2,2.3,mat(0x7E8794));
teava(poz(35.8,25.4,2.1),poz(34.9,26.3,1.6),.14);
[[26.3],[32.8]].forEach(function(w){
[25.5,29,32.5].forEach(function(l){
var lm=new THREE.Mesh(new THREE.BoxGeometry(2.2,.06,.2),
new THREE.MeshBasicMaterial({color:0xFFF6E0}));
lm.position.copy(poz(l,w[0],3.86)); scena.add(lm);
});
});
/* incinta vopsire: perete aspirant cu grila la capatul de iesire */
cutie(22.75,32.8,1.9,.16,3.6,3.4,mat(0x5A6470,{o:.85}),false);
for(var gz=0;gz<5;gz++) cutie(22.83,31.35+gz*.72,1.9,.05,.08,3.0,mat(0x8B93A0),false);
/* poduri: carucior cu tambur si carlig */
[podE,podV].forEach(function(p){
var tb=new THREE.Mesh(new THREE.CylinderGeometry(.22,.22,.7,10),mat(0x6E5A20));
tb.rotation.z=Math.PI/2; tb.position.set(0,1.15,0); p.userData.trolley.add(tb);
});
/* referinta izometrica: palnii conice sub desprafuitoare + scara de acces */
[[13.6,7.4],[13.6,9.4],[17.2,7.4],[17.2,9.4]].forEach(function(pp){
cutie(pp[0],pp[1],0.6,.18,.18,1.2,mat(0x6A7280));
});
var palnie=new THREE.Mesh(new THREE.CylinderGeometry(1.05,.22,1.5,4),mat(0x6A7280));
palnie.rotation.y=Math.PI/4; palnie.position.copy(poz(15.4,8.4,-0.05)); grup.add(palnie);
cutie(12.4,9.6,2.0,1.8,1.6,4.0,mat(0x7E8794,{r:.55}));
var palnie2=new THREE.Mesh(new THREE.CylinderGeometry(.8,.2,1.3,4),mat(0x6A7280));
palnie2.rotation.y=Math.PI/4; palnie2.position.copy(poz(12.4,9.6,0)); grup.add(palnie2);
cutie(17.1,7.3,2.6,.08,.5,5.2,mat(0x3E8E4E));
for(var tt=0.5;tt<5;tt+=.45) cutie(17.1,7.3,tt,.3,.42,.05,mat(0x3E8E4E));
/* ---------- OPERATORI ---------- */
function persoana(cCasca,cVesta,cPant){
var g=new THREE.Group();
var mPant=mat(cPant||0x39424E,{r:.8}), mVesta=mat(cVesta||0xD97B29,{r:.8}), mPiele=mat(0xC9A183,{r:.8});
[-0.09,0.09].forEach(function(x){
var p=new THREE.Mesh(new THREE.BoxGeometry(.15,.75,.15),mPant);
p.position.set(x,.375,0); p.castShadow=true; g.add(p);
});
var tors=new THREE.Mesh(new THREE.BoxGeometry(.42,.6,.24),mVesta);
tors.position.set(0,1.05,0); tors.castShadow=true; g.add(tors);
var cap=new THREE.Mesh(new THREE.SphereGeometry(.13,10,10),mPiele);
cap.position.set(0,1.5,0); g.add(cap);
var casca=new THREE.Mesh(new THREE.SphereGeometry(.15,10,8,0,Math.PI*2,0,Math.PI/2),mat(cCasca,{r:.5}));
casca.position.set(0,1.52,0); g.add(casca);
function brat(x){
var piv=new THREE.Group(); piv.position.set(x,1.32,0);
var b=new THREE.Mesh(new THREE.BoxGeometry(.11,.55,.11),mVesta);
b.position.set(0,-.27,0); b.castShadow=true; piv.add(b); g.add(piv); return piv;
}
var bs=brat(-.27), bd=brat(.27);
bs.rotation.z=.15; bd.rotation.z=-.15;
return {g:g,bs:bs,bd:bd,tors:tors};
}
function pune(om,L,W,rot){om.g.position.copy(poz(L,W,0));om.g.rotation.y=rot||0;scena.add(om.g);return om;}
/* supraveghetor sablare - la pupitrul de langa tunel */
cutie(11.6,6.6,.85,.5,.5,.5,mat(0x5E6670)); // pupitru
var opSab=pune(persoana(0xF2EDE7,0xD97B29),11.6,7.4,Math.PI);
/* operator gaurire+debitare - la dulapul de comanda */
var opGaur=pune(persoana(0xF2EDE7,0xD97B29),45.0,7.3,Math.PI+.4);
/* sudori: 2 pe fiecare zona activa (1 si 2) */
var sudori=[];
[[68.5],[60.5]].forEach(function(z,zi){
var s1=pune(persoana(0xB03030,0x4A5560),z[0]-1.7,30.6,0);
var s2=pune(persoana(0xB03030,0x4A5560),z[0]+1.7,33.0,Math.PI);
sudori.push([s1,s2]);
});
/* pistolete sudura cu varf luminos, doar zona 1 */
function pistolet(){
var g=new THREE.Group();
var corp=new THREE.Mesh(new THREE.BoxGeometry(.08,.3,.08),mat(0x2A2E33));
corp.position.y=-.15; g.add(corp);
var varf=new THREE.Mesh(new THREE.SphereGeometry(.05,8,8),
new THREE.MeshBasicMaterial({color:0xBFDcFF}));
varf.position.y=-.34; g.add(varf); g.userData.varf=varf;
scena.add(g); g.visible=false; return g;
}
var pist1=pistolet(), pist2=pistolet();
/* sablator in camera - cu furtun si duza */
var opCamSab=pune(persoana(0x8899AA,0x8899AA,0x707A85),27.2,27.7,Math.PI*1.5); opCamSab.g.scale.set(0.001,0.001,0.001);
var jetSab=new THREE.Mesh(new THREE.ConeGeometry(.5,2.6,10,1,true),
new THREE.MeshBasicMaterial({color:0xD8C9A0,transparent:true,opacity:.28,side:THREE.DoubleSide}));
jetSab.visible=false; scena.add(jetSab);
var furtunSab=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,4.5,8),mat(0x2A2E33));
furtunSab.visible=false; scena.add(furtunSab);
/* vopsitor in incinta - combinezon alb, pistol cu jet */
var opVops=pune(persoana(0xF2EDE7,0xE8E4DE,0xD8D4CE),31.5,33.9,Math.PI*1.5);
var jetVops=new THREE.Mesh(new THREE.ConeGeometry(.42,2.0,10,1,true),
new THREE.MeshBasicMaterial({color:0xE0483C,transparent:true,opacity:.32,side:THREE.DoubleSide}));
jetVops.visible=false; scena.add(jetVops);
/* ---------- PROFILUL, cu evolutie pe etape ---------- */
var grupProfil=new THREE.Group(); scena.add(grupProfil);
var CUL={
rugina:new THREE.Color(0x8E4B30), /* otel laminat, oxidat */
sa3: new THREE.Color(0xC6CCD4), /* curatat SA 3, metal alb */
cordon:new THREE.Color(0x24242A), /* cordon de sudura brut */
grund: new THREE.Color(0xA85440) /* grund sudabil */
};
var matProfil=new THREE.MeshStandardMaterial({color:CUL.rugina.clone(),roughness:.92,metalness:.15});
matProfil.emissive=new THREE.Color(CUL.rugina); matProfil.emissiveIntensity=.16;
var matCordon=new THREE.MeshStandardMaterial({color:CUL.cordon.clone(),roughness:.75,metalness:.3});
var matGaura =new THREE.MeshStandardMaterial({color:0x15151A,roughness:1});
function grinda(len,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var t1=new THREE.Mesh(new THREE.BoxGeometry(len,.05,.32),m);
var t2=t1.clone(); var inima=new THREE.Mesh(new THREE.BoxGeometry(len,.9,.03),m);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true;g.add(p);});
return g;
}
var profil=grinda(12); grupProfil.add(profil);
/* --- TABLA: varianta alternativa de piesa --- */
function facTabla(len,lat,gros,mat0){
var g=new THREE.Group(), m=mat0||matProfil;
var pl=new THREE.Mesh(new THREE.BoxGeometry(len,gros,lat),m);
pl.castShadow=true; g.add(pl);
return g;
}
var tablaGrup=new THREE.Group(); tablaGrup.visible=false; grupProfil.add(tablaGrup);
var tablaCorp=facTabla(12,1.5,.06); tablaGrup.add(tablaCorp);
/* gauri in tabla, dispuse pe doua randuri */
var tablaGauri=new THREE.Group(); tablaGauri.visible=false; tablaGrup.add(tablaGauri);
[-5.0,-3.5,-2.0,-0.5,1.0,2.5,4.0,5.3].forEach(function(x){
[-0.45,0.45].forEach(function(z){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.085,.085,.09,12),matGaura);
h.position.set(x,0,z); tablaGauri.add(h);
});
});
/* decupaj dreptunghiular, ca la debitarea de tabla */
[-5.95,5.95].forEach(function(x){
var d=new THREE.Mesh(new THREE.BoxGeometry(.22,.08,.5),matGaura);
d.position.set(x,0,0); tablaGauri.add(d);
});
/* nervuri de rigidizare sudate pe tabla + cordoanele lor */
var tablaNerv=new THREE.Group(); tablaNerv.visible=false; tablaGrup.add(tablaNerv);
var tablaCord=new THREE.Group(); tablaCord.visible=false; tablaGrup.add(tablaCord);
[-4.2,-1.4,1.4,4.2].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.05,.42,1.4),matProfil);
n.position.set(x,.24,0); n.castShadow=true; tablaNerv.add(n);
[.03,-.03].forEach(function(dz){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,1.4,8),matCordon);
c.rotation.x=Math.PI/2; c.position.set(x+dz*1.6,.045,0); tablaCord.add(c);
});
});
/* bordura sudata pe cele doua laturi lungi */
[-.72,.72].forEach(function(z){
var b=new THREE.Mesh(new THREE.BoxGeometry(11.6,.30,.05),matProfil);
b.position.set(0,.18,z); b.castShadow=true; tablaNerv.add(b);
var c=new THREE.Mesh(new THREE.CylinderGeometry(.035,.035,11.6,8),matCordon);
c.rotation.z=Math.PI/2; c.position.set(0,.04,z); tablaCord.add(c);
});
/* --- BARE IN PARALEL LA SABLARE ---
Fereastra utila a tunelului are 1.700 x 1.200 mm. Un profil de 306 mm latime
intra de trei ori pe latime, cu spatiu intre bare. La linia de gaurire bratul
de alimentare preia o singura bara, deci barele paralele dispar dupa tunel. */
var N_PARALEL=1, PARALELE=[];
(function(){
[-0.62, 0.62].forEach(function(dz){
var g=new THREE.Group();
var t1=new THREE.Mesh(new THREE.BoxGeometry(12,.05,.32),matProfil);
var t2=t1.clone();
var inima=new THREE.Mesh(new THREE.BoxGeometry(12,.9,.03),matProfil);
t1.position.y=.475; t2.position.y=-.475;
[t1,t2,inima].forEach(function(p){p.castShadow=true; g.add(p);});
g.position.z=dz; g.visible=false;
grupProfil.add(g);
PARALELE.push(g);
});
})();
/* comutatorul profil / tabla */
var MOD_TABLA=false;
/* --- gauri si frezari, apar dupa modulul de gaurire --- */
var gauri=new THREE.Group(); gauri.visible=false; grupProfil.add(gauri);
[-4.6,-3.1,-1.6,1.6,3.1,4.6].forEach(function(x){
[-0.28,0.28].forEach(function(y){
var h=new THREE.Mesh(new THREE.CylinderGeometry(.075,.075,.06,10),matGaura);
h.rotation.x=Math.PI/2; h.position.set(x,y,0); gauri.add(h);
});
/* gauri si pe talpi */
[.478,-.478].forEach(function(y){
var h2=new THREE.Mesh(new THREE.CylinderGeometry(.06,.06,.07,10),matGaura);
h2.position.set(x,y,.11); gauri.add(h2);
});
});
/* frezari la capete */
[-5.85,5.85].forEach(function(x){
var fr=new THREE.Mesh(new THREE.BoxGeometry(.3,.34,.05),matGaura);
fr.position.set(x,0,0); gauri.add(fr);
});
/* --- subansamblu: traverse, nervuri si cordoane de sudura --- */
var traverse=new THREE.Group(); traverse.visible=false; grupProfil.add(traverse);
[-3.6,3.6].forEach(function(dx){
var t=grinda(3.6); t.rotation.y=Math.PI/2; t.position.set(dx,0,0); traverse.add(t);
});
var nervuri=new THREE.Group(); nervuri.visible=false; grupProfil.add(nervuri);
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
var n=new THREE.Mesh(new THREE.BoxGeometry(.04,.86,.3),matProfil);
n.position.set(x,0,0); n.castShadow=true; nervuri.add(n);
});
var cordoane=new THREE.Group(); cordoane.visible=false; grupProfil.add(cordoane);
function cordon(x,y,z,len,rotY,rotZ){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,len,8),matCordon);
c.rotation.z=Math.PI/2; if(rotY) c.rotation.y=rotY; if(rotZ) c.rotation.z+=rotZ;
c.position.set(x,y,z); cordoane.add(c); return c;
}
/* cordoane la nervuri, pe ambele talpi si pe ambele fete */
[-4.8,-2.4,0,2.4,4.8].forEach(function(x){
[.44,-.44].forEach(function(y){
[.15,-.15].forEach(function(z){ cordon(x,y,z,.3,Math.PI/2); });
});
});
/* cordoane la imbinarea traverselor */
[-3.6,3.6].forEach(function(x){
[.44,-.44].forEach(function(y){ cordon(x,y,0,.34,Math.PI/2); });
[.3,-.3].forEach(function(z){
var c=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,.9,8),matCordon);
c.position.set(x,0,z); cordoane.add(c);
});
});
/* --- rotator de sudura: piesa se sprijina si se roteste --- */
var rotator=new THREE.Group(); rotator.visible=false; scena.add(rotator);
[-2.6,2.6].forEach(function(dx){
var cadru=new THREE.Group();
var stalpM=new THREE.Mesh(new THREE.BoxGeometry(.34,2.5,.5),mat(0xE8E4DE,{r:.6}));
stalpM.position.set(dx,1.25,-1.5); cadru.add(stalpM);
var stalpS=stalpM.clone(); stalpS.position.set(dx,1.25,1.5); cadru.add(stalpS);
var trav=new THREE.Mesh(new THREE.BoxGeometry(.3,.3,3.4),mat(0xE8E4DE,{r:.6}));
trav.position.set(dx,2.5,0); cadru.add(trav);
var baza=new THREE.Mesh(new THREE.BoxGeometry(.9,.22,3.9),mat(0xB8B2AC,{r:.7}));
baza.position.set(dx,.11,0); cadru.add(baza);
[-1.5,1.5].forEach(function(dz){
var brat=new THREE.Mesh(new THREE.BoxGeometry(.22,.9,.22),mat(0xC0392B,{r:.55}));
brat.position.set(dx,2.05,dz); cadru.add(brat);
});
var lant=new THREE.Mesh(new THREE.TorusGeometry(.95,.055,7,22),mat(0x3A3F46,{m:.5,r:.4}));
lant.rotation.y=Math.PI/2; lant.position.set(dx,1.15,0); cadru.add(lant);
rotator.add(cadru);
});
var lumProces=new THREE.PointLight(0xffffff,0,18); scena.add(lumProces);
/* ================= SCANTEI ================= */
function facePartic(n,culoare,marime){
var g=new THREE.BufferGeometry(), p=new Float32Array(n*3);
g.setAttribute('position',new THREE.BufferAttribute(p,3));
var m=new THREE.PointsMaterial({color:culoare,size:marime,transparent:true,opacity:.95,
depthWrite:false,blending:THREE.AdditiveBlending});
var pts=new THREE.Points(g,m); pts.visible=false; scena.add(pts);
return {pts:pts,pos:p,vel:new Float32Array(n*3),viata:new Float32Array(n),n:n};
}
var SC_SUD=facePartic(160,0xBFE0FF,.16);
var SC_GAUR=facePartic(120,0xFFC46B,.15);
var SC_SABL=facePartic(220,0xE8D6AE,.10);
function emite(S,origine,forta,imprastiere){
for(var i=0;i<S.n;i++){
if(S.viata[i]<=0&&Math.random()<.22){
S.pos[i*3]=origine.x+(Math.random()-.5)*imprastiere;
S.pos[i*3+1]=origine.y+(Math.random()-.5)*.2;
S.pos[i*3+2]=origine.z+(Math.random()-.5)*imprastiere;
S.vel[i*3]=(Math.random()-.5)*forta;
S.vel[i*3+1]=Math.random()*forta*1.5;
S.vel[i*3+2]=(Math.random()-.5)*forta;
S.viata[i]=.4+Math.random()*.6;
}
}
}
function actualizeazaPartic(S,dt){
var viu=false;
for(var i=0;i<S.n;i++){
if(S.viata[i]>0){
S.viata[i]-=dt; viu=true;
S.vel[i*3+1]-=9.8*dt*.35;
S.pos[i*3]+=S.vel[i*3]*dt; S.pos[i*3+1]+=S.vel[i*3+1]*dt; S.pos[i*3+2]+=S.vel[i*3+2]*dt;
if(S.pos[i*3+1]<.05){S.pos[i*3+1]=.05; S.vel[i*3+1]*=-.35;}
} else { S.pos[i*3+1]=-99; }
}
S.pts.geometry.attributes.position.needsUpdate=true;
S.pts.visible=viu;
}
/* ---------- ETAPE ---------- */
var HL=1.35, HS=0.65;
var ETAPE=[
{n:'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 laser machine',d:'The cleaned profile moves onto station 1 of the laser machine, where it is scanned and positioned.',
t:5,c:[[26,AXA,HL,'tunel'],[40,AXA,HL,'gaurire']]},
{n:'Laser cutting',d:'The laser head cuts the holes, cut-outs and marks on all faces of the profile, in a single clamping.',
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 and cut to length',d:'The head tilted at 45 degrees cuts the bevel and the final length, 11.95 m.',
t:6,c:[[52,AXA,HL,'gaurire'],[58,AXA,HL,'gaurire']],
lum:function(t){var o=OFU('gaurire');
return (t>.3&&t<.85&&Math.random()>.3)?{p:poz(57.5+o.L,AXA+o.W,1.9),c:0xFFD9A0,i:3.0}:null;}},
{n:'Unloading',d:'The cut profile moves onto the unloading rollers, at the end of the machine.',
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:'Zone 1: the part on the rotators, two pulsed MIG/MAG stations; the rotators turn the part without a crane. Zone 2 works on the 6 m table; zone 3 is equipped later.',
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:'Local blasting of the welds',d:'The overhead crane takes the assembly to zone 3, where the welds and the adjacent areas are blasted locally to Sa 2½ with the closed-circuit, dust-free vacuum blasting unit.',
t:9,c:[[68.5,31.8,HS,'sud1'],[52.5,31.8,HS,'sud3']],arc:2.0,pod:'V',
lum:function(t){var o=OFU('sud3');
return t>.45?{p:poz(52.5+o.L+(Math.random()-.5)*3,31.8+o.W,1.3),c:0xFFC86B,i:2.0+Math.random()*1.2}:null;}},
{n:'Transport to the paint booth',d:'The west overhead crane takes the assembly through the manoeuvring aisle, to the entrance of the paint booth.',
t:8,c:[[52.5,31.8,HS,'sud3'],[42,32.8,HS,'camvops']],arc:2.4,pod:'V'},
{n:'Paint booth',d:'Longitudinal airflow, electric heating with heat recovery, forced drying. The part leaves through the opposite end.',
t:10,c:[[42,32.8,HS,'camvops'],[29,32.8,HS,'camvops']],
cul:function(t){var k=Math.min(Math.max((t-.6)/.32,0),1);return CUL.sa3.clone().lerp(CUL.grund,k);},
lum:function(t){var o=OFU('camvops');
return t>.58?{p:poz(29+o.L,32.8+o.W,2.4),c:0xFF8A7A,i:1.8}:null;}},
{n:'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,8,4,6,5,90,15,6,20,5];
var T_REAL_TOTAL=MIN_REALE.reduce(function(a,b){return a+b;},0);
var SCARA_MIN=0.34; /* o secunda de animatie = ~3 minute reale */
ETAPE.forEach(function(e,i){ e.tPrezentare=e.t; e.min=MIN_REALE[i]; });
var TIMP_REAL=false;
function aplicaScaraTimp(){
ETAPE.forEach(function(e,i){ e.t = TIMP_REAL ? e.min*SCARA_MIN : e.tPrezentare; });
T_TOTAL=0; praguri=[];
ETAPE.forEach(function(e){ praguri.push([T_TOTAL,T_TOTAL+e.t]); T_TOTAL+=e.t; });
timp=Math.min(timp,T_TOTAL);
}
var T_TOTAL=0, praguri=[];
ETAPE.forEach(function(e){praguri.push([T_TOTAL,T_TOTAL+e.t]);T_TOTAL+=e.t;});
var cont=document.getElementById('progres-etape');
ETAPE.forEach(function(){var d=document.createElement('div');d.className='pas';cont.appendChild(d);});
var pasi=[].slice.call(cont.children);
function caleP(cale,t,arc){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
var p=poz(a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt, a[2]+(b[2]-a[2])*lt);
if(arc) p.y+=Math.sin(t*Math.PI)*arc;
return p;
}
function caleLW(cale,t){
var n=cale.length-1, seg=Math.min(Math.floor(t*n),n-1), lt=t*n-seg;
var a=pctO(cale[seg]), b=pctO(cale[seg+1]);
return [a[0]+(b[0]-a[0])*lt, a[1]+(b[1]-a[1])*lt];
}
/* ---------- animatie ---------- */
var timp=0, ruleaza=false, urmarire=false;
var btnPlay=document.getElementById('btn-play'), scrub=document.getElementById('scrub'),
viteza=document.getElementById('viteza'), btnUrm=document.getElementById('btn-urmarire');
function miscaPod(pod,Lt,Wt,activ,dt){
var u=pod.userData, k=Math.min(dt*2.2,1);
u.L+=(Lt-u.L)*k; u.Wt+=(Wt-u.Wt)*k;
var Wc=Math.min(Math.max(u.Wt,u.W1+1),u.W2-1);
pod.position.copy(poz(u.L,(u.W1+u.W2)/2,Y_POD));
u.trolley.position.z=(HALA_W/2-Wc)-pod.position.z;
return {L:u.L,W:Wc};
}
function cabluriLa(pod,pTinta){
var u=pod.userData;
var tr=new THREE.Vector3(); u.trolley.getWorldPosition(tr); tr.y-=0.35;
traversa.visible=STRAT_ON.poduri; traversa.position.set(pTinta.x,pTinta.y+.75,pTinta.z);
var pcte=[[tr,new THREE.Vector3(pTinta.x,pTinta.y+.75,pTinta.z)],
[new THREE.Vector3(pTinta.x-3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x-3.5,pTinta.y+.55,pTinta.z)],
[new THREE.Vector3(pTinta.x+3.5,pTinta.y+.75,pTinta.z),new THREE.Vector3(pTinta.x+3.5,pTinta.y+.55,pTinta.z)],
[tr,tr]];
for(var i=0;i<4;i++){
var g=cabluri[i].geometry;
g.setFromPoints(pcte[i]); cabluri[i].visible=STRAT_ON.poduri;
}
}
function ascundeCabluri(){cabluri.forEach(function(c){c.visible=false;});traversa.visible=false;}
var idxCur=0;
function aplica(dt){
var idx=-1;
for(var i=0;i<ETAPE.length;i++) if(timp>=praguri[i][0]&&timp<praguri[i][1]){idx=i;break;}
if(idx<0) idx=ETAPE.length-1;
idxCur=idx;
var e=ETAPE[idx], t=Math.min(Math.max((timp-praguri[idx][0])/e.t,0),1);
var P=caleP(e.c,t,e.arc);
grupProfil.position.copy(P);
traverse.visible = !MOD_TABLA && (idx>8 || (idx===8&&t>.5));
/* --- culoarea corpului --- */
var cul;
if(e.cul) cul=e.cul(t);
else if(idx<2) cul=CUL.rugina;
else if(idx<11) cul=CUL.sa3;
else cul=CUL.grund;
/* fara shop primer, otelul curatat se oxideaza inapoi intre sablare si vopsire */
if(RUST_BACK && idx>=4 && idx<12){
var kr=Math.min((idx-3)/6,1)*0.85;
cul=cul.clone().lerp(CUL_rustBack,kr);
}
matProfil.color.copy(cul); matProfil.emissive.copy(cul);
/* --- gaurile apar in timpul modulului de gaurire --- */
var gaurit = (idx>4) || (idx===4 && t>.45);
var sudat = (idx>8) || (idx===8 && t>.35);
/* barele paralele exista doar pana la intrarea in linia de gaurire (etapele 0-3) */
var inSablare = idx<=3;
PARALELE.forEach(function(g,k){
g.visible = (!MOD_TABLA) && inSablare && (N_PARALEL >= k+2);
});
if(MOD_TABLA){
profil.visible=false; gauri.visible=false;
traverse.visible=false; nervuri.visible=false; cordoane.visible=false;
tablaGrup.visible=true;
tablaGauri.visible=gaurit;
tablaNerv.visible=sudat; tablaCord.visible=sudat;
} else {
tablaGrup.visible=false;
profil.visible=true;
gauri.visible=gaurit;
traverse.visible=sudat; nervuri.visible=sudat; cordoane.visible=sudat;
}
/* --- culoarea cordoanelor: brute, apoi curatate, apoi grunduite --- */
if(idx<9) matCordon.color.copy(CUL.cordon);
else if(idx===9){
var kc=Math.min(Math.max((t-.55)/.4,0),1);
matCordon.color.copy(CUL.cordon.clone().lerp(CUL.sa3,kc));
} else if(idx===10){
matCordon.color.copy(CUL.sa3);
} else if(idx===11){
var kg=Math.min(Math.max((t-.6)/.32,0),1);
var baza=RUST_BACK?CUL_rustBack:CUL.sa3;
matCordon.color.copy(baza.clone().lerp(CUL.grund,kg));
} else matCordon.color.copy(CUL.grund);
/* --- rotatorul de sudura --- */
rotator.visible=(idx===8);
if(idx===8){
rotator.position.copy(P); rotator.position.y=0;
var unghi=Math.sin(performance.now()/1000*.55)*1.15;
grupProfil.rotation.x=unghi;
} else if(idx>8&&idx<12){ grupProfil.rotation.x*=0.92; }
else grupProfil.rotation.x=0;
/* particule pe procese */
if(idx===2){var ot=OFU('tunel'); if(6.5+t*19.5>11&&6.5+t*19.5<21) emite(SC_SABL,poz(16.25+ot.L,AXA+ot.W,1.9),2.2,1.6);}
if(idx===4||idx===5) emite(SC_GAUR,P.clone().setY(P.y+.1),3.4,.5);
if(idx===8) emite(SC_SUD,P.clone().setY(P.y+.35),4.2,1.4);
var l=e.lum?e.lum(t):null;
if(l){lumProces.intensity=l.i;lumProces.color.setHex(l.c);lumProces.position.copy(l.p);}
else lumProces.intensity=0;
// poduri
var lw=caleLW(e.c,t);
ascundeCabluri();
var carE={L:8,W:8}, carV={L:45,W:38};
if(e.pod==='E'){carE={L:lw[0],W:lw[1]};}
else if(e.pod==='V'){carV={L:lw[0],W:lw[1]};}
else if(e.pod==='EV'){ if(lw[1]<20.5) carE={L:lw[0],W:lw[1]}; else carV={L:lw[0],W:lw[1]}; }
var pe=miscaPod(podE,carE.L,carE.W,true,dt||.016);
var pv=miscaPod(podV,carV.L,carV.W,true,dt||.016);
if(e.pod==='E') cabluriLa(podE,P);
if(e.pod==='V') cabluriLa(podV,P);
if(e.pod==='EV') cabluriLa(lw[1]<20.5?podE:podV,P);
// brat alimentare urmareste profilul pe linia de gaurire
var og=OFU('gaurire');
var Lc=Math.min(Math.max(lw[0]-6,33+og.L),59.5+og.L);
if(idx>=3&&idx<=6) carucior.position.copy(poz(Lc,6.5+og.W,1.0));
var TA=performance.now()/1000;
// turbine se invart cand tunelul lucreaza
var tunelActiv=(idx===2);
ANIM.turbine.forEach(function(d){ if(tunelActiv) d.rotation.y+=0.9; });
if(ANIM.perdea) ANIM.perdea.material.opacity=tunelActiv?(.10+Math.random()*.12):0;
// axele de gaurire lucreaza cand piesa e sub portal
var gaurire=(idx===4);
if(ANIM.axV){
ANIM.axV.position.y=ANIM.axVy+(gaurire?(-0.35+0.35*Math.abs(Math.sin(TA*3))):0);
var c1=Math.sin(TA*2.6), c2=Math.sin(TA*2.6+1.5);
ANIM.axH1.position.z=ANIM.axH1z+(gaurire? -0.3*Math.max(0,c1):0);
ANIM.axH2.position.z=ANIM.axH2z+(gaurire? 0.3*Math.max(0,c2):0);
if(gaurire){ANIM.axV.rotation.y+=1.1;}
}
// sudorii din zona 1 lucreaza in etapa de sudura
var sudActiv=(idx===8);
pist1.visible=sudActiv&&STRAT_ON.operatori; pist2.visible=sudActiv&&STRAT_ON.operatori;
if(sudActiv){
var s1=sudori[0][0], s2=sudori[0][1];
s1.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2);
s2.bd.rotation.x=-1.15+0.12*Math.sin(TA*2.2+1);
s1.tors.rotation.x=.18; s2.tors.rotation.x=.18;
var h1=new THREE.Vector3(); s1.bd.getWorldPosition(h1);
var h2=new THREE.Vector3(); s2.bd.getWorldPosition(h2);
pist1.position.set(h1.x+.15,h1.y-.1,h1.z-(0.35));
pist2.position.set(h2.x-.15,h2.y-.1,h2.z+(0.35));
pist1.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist2.userData.varf.material.opacity=Math.random()>.3?1:.2;
pist1.userData.varf.material.transparent=true; pist2.userData.varf.material.transparent=true;
} else {
sudori[0][0].bd.rotation.x=0; sudori[0][1].bd.rotation.x=0;
sudori[0][0].tors.rotation.x=0; sudori[0][1].tors.rotation.x=0;
}
// sablatorul lucreaza cand piesa e in camera
var sabActiv=false;
jetSab.visible=sabActiv&&STRAT_ON.operatori; furtunSab.visible=false;
if(sabActiv){
var bal=Math.sin(TA*1.4)*2.6;
var ocs=OFU('camsab');
opCamSab.g.position.copy(poz(29+bal+ocs.L,27.7+ocs.W,0));
opCamSab.bd.rotation.x=-1.25;
jetSab.position.copy(poz(29+bal+ocs.L,27.0+ocs.W,1.35));
jetSab.rotation.x=Math.PI/2+.25;
jetSab.material.opacity=.18+Math.random()*.15;
furtunSab.position.copy(poz(29+bal+1.5+ocs.L,28.6+ocs.W,.8));
furtunSab.rotation.z=1.2; furtunSab.rotation.y=.4;
} else opCamSab.bd.rotation.x=0;
// vopsitorul lucreaza in etapa de vopsire
var vopsActiv=(idx===11&&t>.55);
jetVops.visible=vopsActiv&&STRAT_ON.operatori;
if(vopsActiv){
var bal2=Math.sin(TA*1.1)*3.0;
var ocv=OFU('camvops');
opVops.g.position.copy(poz(29+bal2+ocv.L,33.9+ocv.W,0));
opVops.bd.rotation.x=-1.2+0.15*Math.sin(TA*3);
jetVops.position.copy(poz(29+bal2+ocv.L,33.35+ocv.W,1.3));
jetVops.rotation.x=Math.PI/2+.2;
jetVops.material.opacity=.22+Math.random()*.14;
} else opVops.bd.rotation.x=0;
if(TIMP_REAL){
var scurs=0; for(var q=0;q<idx;q++) scurs+=ETAPE[q].min;
scurs+=ETAPE[idx].min*t;
document.getElementById('et-timp').textContent=
'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 laser cutting machine for profiles and plate'},
{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 18 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+' bare in paralel prin tunel. Fereastra de 1.700 x 1.200 mm le preia simultan; capacitatea de sablare se multiplica, iar masina laser preia apoi cate o bara.');
} else {
toast('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, laser cutting, 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:'Laser cutting',min:12},
{n:'Welding', min:90},
{n:'Sablare cordoane (vid)', min:15},
{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='linie_laser_flux3D_'+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.5)?'sablare':'gaurire';
return (L>=45)?'sudura':'camere';
}
grup.children.slice().forEach(function(o){
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
scena.children.slice().forEach(function(o){
if(o===grup||o===grupProfil||o.isLight) return;
var st=autoStrat(o); if(st&&STRATURI[st]) STRATURI[st].push(o);
});
document.querySelectorAll('#straturi input').forEach(function(cb){
cb.addEventListener('change',function(){
var st=cb.getAttribute('data-strat');
STRAT_ON[st]=cb.checked;
STRATURI[st].forEach(function(o){o.visible=cb.checked;});
});
});
})();
/* ============ COTARE VIE + DETECTIE COLIZIUNI ============ */
var COT_LIMITA=1.2; /* culoar minim admis intre echipamente, m */
var conturDrag=null, cotaSprite=[], conturCol=null;
function faceContur(culoare){
var g=new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(),new THREE.Vector3(),
new THREE.Vector3(),new THREE.Vector3(),new THREE.Vector3()]);
var l=new THREE.Line(g,new THREE.LineBasicMaterial({color:culoare,linewidth:2}));
l.visible=false; scena.add(l); return l;
}
conturDrag=faceContur(0x2ECC71); conturCol=faceContur(0xE74C3C);
function spriteText(){
var c=document.createElement('canvas'); c.width=256; c.height=64;
var t=new THREE.CanvasTexture(c);
var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false}));
sp.scale.set(7,1.75,1); sp.visible=false; scena.add(sp);
sp.userData={cv:c,tx:t};
return sp;
}
for(var ci=0;ci<4;ci++) cotaSprite.push(spriteText());
function scrieSprite(sp,txt,culoare){
var c=sp.userData.cv, g=c.getContext('2d');
g.clearRect(0,0,256,64);
g.font='600 30px Segoe UI, sans-serif'; g.textAlign='center';
var w=g.measureText(txt).width+26;
g.fillStyle='rgba(16,12,18,.92)'; g.fillRect((256-w)/2,10,w,44);
g.fillStyle=culoare; g.fillText(txt,128,42);
sp.userData.tx.needsUpdate=true; sp.visible=true;
}
function limiteUnitate(u){
return {L1:u.b[0]+u.off.x, L2:u.b[1]+u.off.x, W1:u.b[2]-u.off.z, W2:u.b[3]-u.off.z};
}
function verificaColiziuni(u){
var a=limiteUnitate(u), rez={ok:true,cu:null};
if(a.L1<0.2||a.L2>74.6||a.W1<0.2||a.W2>42.6){rez.ok=false;rez.cu='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 laser, tunel, posturi si camera de vopsire */
(function(){
var y=6.2;
tub(poz(2,1.2,y),poz(72,1.2,y),.13,0x2E86DE,'aer');
[16,48,68].forEach(function(L){ tub(poz(L,1.2,y),poz(L,1.2,1.6),.10,0x2E86DE,'aer'); });
tub(poz(29,1.2,y),poz(29,30.6,y),.12,0x2E86DE,'aer');
tub(poz(29,30.6,y),poz(29,30.6,2.2),.10,0x2E86DE,'aer');
})();
/* alimentare electrica - galben, jgheab principal + coborari la tablouri */
(function(){
var y=7.0;
tub(poz(4,21.5,y),poz(72,21.5,y),.16,0xF1C40F,'electric');
[[16,4],[48,4],[29,32.8],[60,31.8]].forEach(function(p){
tub(poz(p[0],21.5,y),poz(p[0],p[1],y),.11,0xF1C40F,'electric');
tub(poz(p[0],p[1],y),poz(p[0],p[1],2.0),.09,0xF1C40F,'electric');
});
})();
/* aspiratie - gri, de la zonele de sudura si camere spre exterior */
(function(){
var y=5.6;
tub(poz(52,40.5,y),poz(70,40.5,y),.3,0x95A5A6,'aspiratie');
[52.5,60.5,68.5].forEach(function(L){ tub(poz(L,40.5,y),poz(L,34,y),.22,0x95A5A6,'aspiratie'); });
tub(poz(29,26.3,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,32.8,4.4),poz(29,22,5.6),.26,0x95A5A6,'aspiratie');
tub(poz(29,22,5.6),poz(70,22,5.6),.3,0x95A5A6,'aspiratie');
})();
/* ================= MUTARE ECHIPAMENTE (drag & drop) ================= */
var MUTARE=false, dragUnit=null;
var UNITATI=[
{id:'tunel', nume:'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:'Laser cutting machine',b:[32.5,68.5, 0.6,9.6]},
{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:'Zone 3 - weld blasting', b:[48.4,56.4,22.2,42.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>
