{% load static %}
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Building Twin — Babylon Wireframe Stadium + Smart Can</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <!-- Babylon core + loaders (for GLB/GLTF/OBJ) -->
  <script src="https://cdn.babylonjs.com/babylon.js"></script>
  <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
  <style>
    html, body { height:100%; margin:0; background:#0b1220; font-family:system-ui, sans-serif; }
    #c {
      position: fixed;
      left: 0; top: 0;
      width: 100vw; height: 100vh;   /* fill viewport */
      display: block;
    }
    #hud {
      position: fixed; bottom: 10px; left: 10px; z-index: 10; color: #e5e7eb;
      background:#0f172a; padding:10px 12px; border-radius:10px; box-shadow:0 10px 30px rgba(0,0,0,.35);
      font-size:12px; line-height:1.25; width: 300px;
    }
    .row { display:flex; gap:8px; margin-top:6px; }
    .row > * { flex:1; }
    label { display:flex; align-items:center; gap:6px; cursor:pointer; }
    button { width:100%; background:#1f2937; color:#e5e7eb; border:1px solid #334155;
             border-radius:8px; padding:6px 8px; cursor:pointer; }
    button:hover { background:#111827; }
    .muted { opacity:.75 }
  </style>
</head>
<body>
  <canvas id="c"></canvas>

  <div id="hud">
    <div style="font-weight:600; margin-bottom:6px;">Wireframe Stadium (Babylon) + Smart Can</div>
    <div class="muted">Orbit with mouse/touch. Drag bins on the floor.</div>
    <div class="row" style="margin-top:8px;">
      <label><input type="checkbox" id="chkL1" checked> L1 Concourse</label>
      <label><input type="checkbox" id="chkL2" checked> L2 Mezzanine</label>
    </div>
    <div class="row">
      <label><input type="checkbox" id="chkL3" checked> L3 Upper Stands</label>
      <label><input type="checkbox" id="chkBins" checked> Bins</label>
    </div>
    <div class="row" style="margin-top:8px;">
      <button id="btnRegen">Regenerate</button>
      <button id="btnResetCam">Reset Camera</button>
      <button id="btnViewer">Open 2D Banorte Stadium</button>
    </div>
    <div class="muted" style="margin-top:6px;">
      URL knobs: <code>?seed=123&can_units=m|cm|mm&can_scale=1&can_angle=45&can_y=0</code>
    </div>
  </div>

  <script>
    // ---------- Params ----------
    const params = new URLSearchParams(location.search);
    const canUnits = (params.get('can_units') || 'm').toLowerCase(); // m|cm|mm
    const canScaleUser = parseFloat(params.get('can_scale') || '1');
    const canAngleDeg  = parseFloat(params.get('can_angle') || (Math.random()*360).toFixed(1));
    const canYOffset   = parseFloat(params.get('can_y') || '0');

    const unitToMeter  = canUnits === 'cm' ? 0.01 : canUnits === 'mm' ? 0.001 : 1;
    const CAN_GLb_URL  = "{% static 'pxy_building_digital_twins/models/smartcan.glb' %}";
    const CAN_OBJ_URL  = "{% static 'pxy_building_digital_twins/models/smartcan.obj' %}";

    // ---------- Engine / Scene ----------
    const canvas = document.getElementById('c');
    const engine = new BABYLON.Engine(canvas, true, { antialias:true, preserveDrawingBuffer:true, stencil:true });
    const scene = new BABYLON.Scene(engine);
    function fitCanvas(){ canvas.style.width='100vw'; canvas.style.height='100vh'; canvas.width=canvas.clientWidth; canvas.height=canvas.clientHeight; engine.resize(); }
    fitCanvas(); window.addEventListener('resize', fitCanvas);
    scene.clearColor = new BABYLON.Color4(0.043,0.071,0.125,1.0);

    // Camera
    const camera = new BABYLON.ArcRotateCamera("cam", Math.PI*1.25, 1.05, 220, new BABYLON.Vector3(0,10,0), scene);
    camera.attachControl(canvas, true);
    camera.lowerRadiusLimit = 40; camera.upperRadiusLimit = 2000;

    // Lights
    const hemi = new BABYLON.HemisphericLight("h", new BABYLON.Vector3(0,1,0), scene);
    hemi.intensity = 0.9;
    const dir = new BABYLON.DirectionalLight("d", new BABYLON.Vector3(-1,-2,-1), scene);
    dir.position = new BABYLON.Vector3(120,180,120); dir.intensity = 0.7;

    // Axes
    const SHOW_AXES = false;
if (SHOW_AXES){
    (function axes(len=30){
      const mk=(a,b,c,col)=>{const l=BABYLON.MeshBuilder.CreateLines("ax",{points:[a,b,c]},scene); l.color=col; l.isPickable=false; return l;};
      mk(new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(len,0,0), new BABYLON.Vector3(len,0,0), new BABYLON.Color3(1,0.2,0.2));
      mk(new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(0,len,0), new BABYLON.Vector3(0,len,0), new BABYLON.Color3(0.2,1,0.2));
      mk(new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(0,0,len), new BABYLON.Vector3(0,0,len), new BABYLON.Color3(0.3,0.7,1));
    })();
}
    // Subtle grid
    (function grid(){
      const size=1000, step=10, y=0, col=new BABYLON.Color3(0.18,0.22,0.29);
      for(let x=-size; x<=size; x+=step){
        const l=BABYLON.MeshBuilder.CreateLines("gx",{points:[new BABYLON.Vector3(x,y,-size),new BABYLON.Vector3(x,y,size)]},scene);
        l.color=col; l.isPickable=false;
      }
      for(let z=-size; z<=size; z+=step){
        const l=BABYLON.MeshBuilder.CreateLines("gz",{points:[new BABYLON.Vector3(-size,y,z),new BABYLON.Vector3(size,y,z)]},scene);
        l.color=col; l.isPickable=false;
      }
    })();

    // ---------- RNG ----------
    function mulberry32(a){ return function(){ var t=a+=0x6D2B79F5; t=Math.imul(t^t>>>15,t|1); t^=t+Math.imul(t^t>>>7,t|61); return ((t^t>>>14)>>>0)/4294967296; } }
    function makeRand(seedStr){
      const s = seedStr ? Array.from(String(seedStr)).reduce((acc,ch)=>acc+ch.charCodeAt(0),0) : Math.floor(Math.random()*1e9);
      const r = mulberry32(s); return { f:(a,b)=>a+(b-a)*r(), i:(a,b)=>Math.floor(a+(b-a+1)*r()), s };
    }
    const querySeed = new URLSearchParams(location.search).get("seed");

    // ---------- Helpers ----------
    function wireRect(name, w, d, y, color3){
  const hw = w/2, hd = d/2;
  const pts = [
    new BABYLON.Vector3(-hw, y, -hd),
    new BABYLON.Vector3( hw, y, -hd),
    new BABYLON.Vector3( hw, y,  hd),
    new BABYLON.Vector3(-hw, y,  hd),
    new BABYLON.Vector3(-hw, y, -hd) // close loop
  ];
  const ln = BABYLON.MeshBuilder.CreateLines(name, { points: pts }, scene);
  ln.color = color3; ln.isPickable = false; 
  return ln;
}


    function wireBox(name,w,d,h,y,color3){
      const box=BABYLON.MeshBuilder.CreateBox(name,{width:w,depth:d,height:h},scene);
      const mat=new BABYLON.StandardMaterial(name+"_m",scene); mat.wireframe=true; mat.diffuseColor=color3; mat.specularColor=BABYLON.Color3.Black();
      box.material=mat; box.position.y=y; box.isPickable=false; return box;
    }
    function wireEllipse(name,rx,rz,y,seg,color3){
      const pts=[]; for(let i=0;i<=seg;i++){const a=(i/seg)*Math.PI*2; pts.push(new BABYLON.Vector3(rx*Math.cos(a),y,rz*Math.sin(a))); }
      const ln=BABYLON.MeshBuilder.CreateLines(name,{points:pts,updatable:false},scene); ln.color=color3; ln.isPickable=false; return ln;
    }
    function addRib(name,x1,y1,z1,x2,y2,z2,color3){
      const ln=BABYLON.MeshBuilder.CreateLines(name,{points:[new BABYLON.Vector3(x1,y1,z1),new BABYLON.Vector3(x2,y2,z2)]},scene);
      ln.color=color3; ln.isPickable=false; return ln;
    }
    function addBin(name,x,y,z,color3){
      const s=BABYLON.MeshBuilder.CreateSphere(name,{diameter:2.8,segments:10},scene);
      const mat=new BABYLON.StandardMaterial(name+"_m",scene); mat.emissiveColor=color3; mat.diffuseColor=color3.scale(0.35); s.material=mat;
      s.position.set(x,y,z);
      const drag=new BABYLON.PointerDragBehavior({dragPlaneNormal:new BABYLON.Vector3(0,1,0)}); s.addBehavior(drag);
      return s;
    }

    // Groups
    const L1=new BABYLON.TransformNode("L1_Concourse",scene);
    const L2=new BABYLON.TransformNode("L2_Mezzanine",scene);
    const L3=new BABYLON.TransformNode("L3_Upper",scene);
    const BINS=new BABYLON.TransformNode("Bins",scene);
    const CAN_PARENT=new BABYLON.TransformNode("SmartCan",scene);

    // Camera framing
    function frameCamera(maxRadius,yCenter){ camera.alpha=Math.PI*1.25; camera.beta=1.05; camera.radius=Math.max(120,maxRadius*2.2); camera.setTarget(new BABYLON.Vector3(0,yCenter,0)); }

    // ---------- Smart Can loader ----------
    function splitURL(u){ const i=u.lastIndexOf('/'); return {root:u.slice(0,i+1), file:u.slice(i+1)}; }

    function addMarker(x, y, z) {
        // vertical neon line + torus ring
        const ring = BABYLON.MeshBuilder.CreateTorus("canRing", {diameter: 8, thickness: 0.6, tessellation: 32}, scene);
        const matR = new BABYLON.StandardMaterial("canRingM", scene); matR.emissiveColor = new BABYLON.Color3(1, 0.5, 0.1);
        ring.material = matR; ring.position.set(x, y, z);

        const line = BABYLON.MeshBuilder.CreateLines("canLine", {points:[
            new BABYLON.Vector3(x, y, z),
            new BABYLON.Vector3(x, y + 10, z)
        ]}, scene);
        line.color = new BABYLON.Color3(1, 0.8, 0.2);
        line.isPickable = false;

        return ring;
        }
async function loadSmartCanAt(x, y, z, yawDeg, scaleMeters){
  const qs = new URLSearchParams(location.search);
  const desiredH = parseFloat(qs.get("can_h") || "1.1");          // meters
  const wireOn   = (qs.get("can_wire") ?? "1") !== "0";

  // NEW: manual rotation overrides (degrees)
  const rxDeg = parseFloat(qs.get("can_rx") || "90");
  const ryDeg = parseFloat(qs.get("can_ry") || "0");
  const rzDeg = parseFloat(qs.get("can_rz") || "0");

  const urls = [
    "{% static 'pxy_building_digital_twins/models/smartcan.glb' %}",
    "{% static 'pxy_building_digital_twins/models/smartcan.obj' %}"
  ];

  async function tryUrl(url){
    try { return (await BABYLON.SceneLoader.ImportMeshAsync("", "", url, scene)).meshes; }
    catch {
      const i = url.lastIndexOf("/"); const root = url.slice(0,i+1), file = url.slice(i+1);
      try { return (await BABYLON.SceneLoader.ImportMeshAsync("", root, file, scene)).meshes; } catch { return null; }
    }
  }

  let loaded=null;
  for (const u of urls){ loaded = await tryUrl(u); if (loaded && loaded.length) break; }
  if (!loaded){ console.error("[smartcan] FAILED to load"); return; }

  // Parent & visible
  loaded.forEach(m => { if (!m.parent) m.parent = CAN_PARENT; if (!m.material){ const t=new BABYLON.StandardMaterial("canWhite",scene); t.emissiveColor=new BABYLON.Color3(0.95,0.95,0.95); t.diffuseColor=t.emissiveColor; m.material=t; } });

  // Helper to compute bounds in current world transform
  const bounds = () => {
    let min=new BABYLON.Vector3(+Infinity,+Infinity,+Infinity),
        max=new BABYLON.Vector3(-Infinity,-Infinity,-Infinity);
    loaded.forEach(m => {
      const bi = m.getBoundingInfo?.(); if (!bi) return;
      min = BABYLON.Vector3.Minimize(min, bi.boundingBox.minimumWorld);
      max = BABYLON.Vector3.Maximize(max, bi.boundingBox.maximumWorld);
    });
    return {min, max, size: max.subtract(min)};
  };

  // Reset transforms
  CAN_PARENT.position.set(0,0,0);
  CAN_PARENT.rotationQuaternion = BABYLON.Quaternion.Identity();
  CAN_PARENT.scaling.setAll(1);

  // Auto-orient to make the tallest axis become Y
  const cands = [
    BABYLON.Quaternion.Identity(),
    BABYLON.Quaternion.FromEulerAngles(Math.PI/2, 0, 0),
    BABYLON.Quaternion.FromEulerAngles(-Math.PI/2, 0, 0),
    BABYLON.Quaternion.FromEulerAngles(0, 0, Math.PI/2),
    BABYLON.Quaternion.FromEulerAngles(0, 0, -Math.PI/2),
    BABYLON.Quaternion.FromEulerAngles(Math.PI, 0, 0),
    BABYLON.Quaternion.FromEulerAngles(0, 0, Math.PI)
  ];
  let bestQ = cands[0], bestH = -1;
  for (const q of cands){
    CAN_PARENT.rotationQuaternion = q;
    const b = bounds();
    if (b.size.y > bestH){ bestH = b.size.y; bestQ = q; }
  }
  CAN_PARENT.rotationQuaternion = bestQ;

  // Auto-scale height to desiredH, then apply user scaleMeters
  let b0 = bounds();
  const scaleAuto  = b0.size.y > 0 ? desiredH / b0.size.y : 1;
  const finalScale = scaleMeters * scaleAuto;
  CAN_PARENT.scaling.setAll(finalScale);

  // NEW: apply manual Euler corrections (degrees) BEFORE yaw
  const rFix = BABYLON.Quaternion.FromEulerAngles(
    BABYLON.Angle.FromDegrees(rxDeg).radians(),
    BABYLON.Angle.FromDegrees(ryDeg).radians(),
    BABYLON.Angle.FromDegrees(rzDeg).radians()
  );
  CAN_PARENT.rotationQuaternion = CAN_PARENT.rotationQuaternion.multiply(rFix);

  // Apply yaw facing
  const yawQ = BABYLON.Quaternion.FromEulerAngles(0, BABYLON.Angle.FromDegrees(yawDeg).radians(), 0);
  CAN_PARENT.rotationQuaternion = CAN_PARENT.rotationQuaternion.multiply(yawQ);

  // Recompute bounds AFTER all rotations+scaling, then sit the base on the floor
  const b1 = bounds();
  const lift = -b1.min.y;               // already in world units now
  CAN_PARENT.position.set(x, y + lift, z);

  // Optional wireframe overlay
  if (wireOn){
    loaded.forEach(m => {
      if (!m.geometry) return;
      const wf = m.clone(m.name+"_wf");
      const mat = new BABYLON.StandardMaterial("wfM", scene);
      mat.wireframe = true; mat.emissiveColor = new BABYLON.Color3(0.05,0.05,0.05);
      wf.material = mat; wf.isPickable = false;
      wf.parent = m.parent;
      wf.scaling = m.scaling.multiply(new BABYLON.Vector3(1.001,1.001,1.001));
      wf.rotationQuaternion = m.rotationQuaternion?.clone() || BABYLON.Quaternion.Identity();
      wf.position = m.position.clone();
    });
  }
}



    // ---------- Builder ----------
    function buildStadium(seedStr){
      [L1,L2,L3,BINS].forEach(n => n.getChildren().slice().forEach(ch => ch.dispose()));
      CAN_PARENT.getChildren().slice().forEach(ch => ch.dispose());

      const R=makeRand(seedStr), rf=R.f, ri=R.i;

      // Heights
      const Y_FIELD=0.2, Y_CONC=2.0, Y_MEZZ=8.0, Y_STAND=14.0;

      // Field
      const FIELD_W=rf(100,110), FIELD_D=rf(64,72);
      wireRect("field", FIELD_W, FIELD_D, Y_FIELD, BABYLON.Color3.FromHexString("#16a34a"));


      // Rings
      const CONC_RX_IN=rf(62,68), CONC_RZ_IN=rf(56,64);
      const CONC_RX_OUT=CONC_RX_IN+rf(6,10), CONC_RZ_OUT=CONC_RZ_IN+rf(6,10);

      const MEZZ_RX_IN=CONC_RX_OUT+rf(2,4), MEZZ_RZ_IN=CONC_RZ_OUT+rf(2,4);
      const MEZZ_RX_OUT=MEZZ_RX_IN+rf(8,12), MEZZ_RZ_OUT=MEZZ_RZ_IN+rf(8,12);

      const STANDS_RX_IN=MEZZ_RX_OUT+rf(3,5), STANDS_RZ_IN=MEZZ_RZ_OUT+rf(3,5);
      const STANDS_RX_OUT=STANDS_RX_IN+rf(18,26), STANDS_RZ_OUT=STANDS_RZ_IN+rf(18,26);

      wireEllipse("conc_in", CONC_RX_IN, CONC_RZ_IN, Y_CONC, 256, new BABYLON.Color3(0.10,0.94,0.86)).parent=L1;
      wireEllipse("conc_out", CONC_RX_OUT, CONC_RZ_OUT, Y_CONC, 256, new BABYLON.Color3(0.00,0.75,1.00)).parent=L1;
      wireEllipse("mezz_in", MEZZ_RX_IN, MEZZ_RZ_IN, Y_MEZZ, 256, new BABYLON.Color3(0.98,0.84,0.22)).parent=L2;
      wireEllipse("mezz_out", MEZZ_RX_OUT, MEZZ_RZ_OUT, Y_MEZZ, 256, new BABYLON.Color3(1.00,0.66,0.10)).parent=L2;
      wireEllipse("stand_in", STANDS_RX_IN, STANDS_RZ_IN, Y_STAND, 256, new BABYLON.Color3(0.80,0.52,1.00)).parent=L3;
      wireEllipse("stand_out", STANDS_RX_OUT, STANDS_RZ_OUT, Y_STAND, 256, new BABYLON.Color3(0.62,0.82,1.00)).parent=L3;

      // Ribs
      const ribCol=new BABYLON.Color3(0.10,0.85,0.75);
      const ribs1=ri(12,18);
      for(let i=0;i<ribs1;i++){
        const a=i/ribs1*Math.PI*2+rf(-0.03,0.03);
        const x1=CONC_RX_OUT*Math.cos(a), z1=CONC_RZ_OUT*Math.sin(a);
        const x2=MEZZ_RX_IN*Math.cos(a),  z2=MEZZ_RZ_IN*Math.sin(a);
        const ln=addRib("rib12_"+i, x1,Y_CONC,z1, x2,Y_MEZZ,z2, ribCol); ln.parent=L1;
      }
      const ribs2=ri(12,18);
      for(let i=0;i<ribs2;i++){
        const a=i/ribs2*Math.PI*2+rf(-0.03,0.03);
        const x1=MEZZ_RX_OUT*Math.cos(a), z1=MEZZ_RZ_OUT*Math.sin(a);
        const x2=STANDS_RX_IN*Math.cos(a), z2=STANDS_RZ_IN*Math.sin(a);
        const ln=addRib("rib23_"+i, x1,Y_MEZZ,z1, x2,Y_STAND,z2, ribCol); ln.parent=L2;
      }

      // Bins
      const midRX=(CONC_RX_IN+CONC_RX_OUT)/2, midRZ=(CONC_RZ_IN+CONC_RZ_OUT)/2;
      const colors=[new BABYLON.Color3(0.20,1.00,0.65), new BABYLON.Color3(0.10,0.80,1.00), new BABYLON.Color3(0.95,0.55,1.00), new BABYLON.Color3(1.00,0.70,0.25)];
      const count=ri(40,60);
      for(let i=0;i<count;i++){
        const a=i/count*Math.PI*2+rf(-0.05,0.05);
        const x=midRX*Math.cos(a), z=midRZ*Math.sin(a);
        const s=addBin("bin_"+i, x, Y_CONC+0.5, z, colors[i%colors.length]); s.parent=BINS;
      }

const angleRad = canAngleDeg * Math.PI/180;
const canX = midRX * Math.cos(angleRad);
const canZ = midRZ * Math.sin(angleRad);
const canScaleMeters = unitToMeter * canScaleUser;

// 🔶 drop a neon marker where the can should be (so you know the spot)
addMarker(canX, Y_CONC + canYOffset, canZ);

// log what we’re about to do
console.log("[smartcan] pos=", {x: canX, y: Y_CONC + canYOffset, z: canZ},
            "yawDeg=", -canAngleDeg, "scaleMeters=", canScaleMeters);

// load the model there
loadSmartCanAt(canX, Y_CONC + canYOffset, canZ, -canAngleDeg, canScaleMeters);

      // Frame camera
      const maxR=Math.max(STANDS_RX_OUT, STANDS_RZ_OUT);
      frameCamera(maxR, (Y_CONC + Y_STAND)/2);
    }

    // Build + UI
    buildStadium(new URLSearchParams(location.search).get("seed"));
    const $=q=>document.querySelector(q);
    $("#chkL1").onchange=e=>L1.setEnabled(e.target.checked);
    $("#chkL2").onchange=e=>L2.setEnabled(e.target.checked);
    $("#chkL3").onchange=e=>L3.setEnabled(e.target.checked);
    $("#chkBins").onchange=e=>BINS.setEnabled(e.target.checked);
    $("#btnRegen").onclick=()=>buildStadium(Math.floor(Math.random()*1e9).toString());
    $("#btnResetCam").onclick=()=>frameCamera(200,10);

    engine.runRenderLoop(()=>scene.render());
    window.addEventListener('resize',()=>engine.resize());

// Open Leaflet viewer, preserving current query params
document.getElementById('btnViewer').onclick = () => {
  const params = new URLSearchParams(location.search);
  if (!params.has('seed')) params.set('seed', Date.now());   // keep determinism if you provided one
  location.href = `/building/twin/viewer/?${params.toString()}`;
};


  </script>
</body>
</html>
