<!-- RUBY-BACKUP · part 374 -->
A_MAX_DIMS.z) PROVOLTA_MAX_DIMS.z = s.z;
  }catch(e){}
}
function enforceHeightLaw(){
  if(!SELECTED_PART) return false;
  if(PROVOLTA_MAX_DIMS.x <= 0) return false;
  try{
    var box = new THREE.Box3().setFromObject(SELECTED_PART);
    var s = box.getSize(new THREE.Vector3());
    var cs = SELECTED_PART.scale;
    var clamped = false;
    /* each axis: part can't exceed her max in that axis */
    if(s.x > PROVOLTA_MAX_DIMS.x){
      cs.x *= PROVOLTA_MAX_DIMS.x / s.x;
      clamped = true;
    }
    if(s.y > PROVOLTA_MAX_DIMS.y){
      cs.y *= PROVOLTA_MAX_DIMS.y / s.y;
      clamped = true;
    }
    if(s.z > PROVOLTA_MAX_DIMS.z){
      cs.z *= PROVOLTA_MAX_DIMS.z / s.z;
      clamped = true;
    }
    if(clamped){
      SELECTED_PART.scale.copy(cs);
      return true;
    }
  }catch(e){}
  return false;
}
function applyDimsLive(){
  if(!SELECTED_PART) return;
  try{
    var tw = parseFloat(_dimsInputs.w.value);
    var th = parseFloat(_dimsInputs.h.value);
    var td = parseFloat(_dimsInputs.d.value);
    if(!(tw > 0 && th > 0 && td > 0)) return;
    var box = new THREE.Box3().setFromObject(SELECTED_PART);
    var cs = box.getSize(new THREE.Vector3());
    var cur = SELECTED_PART.scale;
    /* avoid divide by zero on first drag */
    if(cs.x < 0.001 || cs.y < 0.001 || cs.z < 0.001) return;
    var nsx = cur.x * (tw / cs.x);
    var nsy = cur.y * (th / cs.y);
    var nsz = cur.z * (td / cs.z);
    /* clamp to sane */
    nsx = Math.max(0.01, Math.min(10, nsx));
    nsy = Math.max(0.01, Math.min(10, nsy));
    nsz = Math.max(0.01, Math.min(10, nsz));
    SELECTED_PART.scale.set(nsx, nsy, nsz);
    /* the law: never beyond her height */
    if(enforceHeightLaw()){
      codeStatus('held at her measure — no part bigger than she is');
    }
  }catch(e){}
}
function hideDimsPanel(){
  if(_dimsPanel){
    try{ _dimsPanel.remove(); }catch(e){}
    _dimsPanel = null;
    _dimsInputs = {};
  }
}
function applyDimsToSelected(){
  if(!SELECTED_PART){ codeStatus('select a part first'); return; }
  try{
    var tw = parseFloat(_dimsInputs.w.value);
    var th = parseFloat(_dimsInputs.h.value);
    var td = parseFloat(_dimsInputs.d.value);
    if(!(tw > 0 && th > 0 && td > 0)){ codeStatus('enter positive numbers'); return; }
    /* measure current */
    var box = new THREE.Box3().setFromObject(SELECTED_PART);
    var cs = box.getSize(new THREE.Vector3());
    /* scale to target — account for current scale */
    var cur = SELECTED_PART.scale;
    var nsx = cur.x * (tw / cs.x);
    var nsy = cur.y * (th / cs.y);
    var nsz = cur.z * (td / cs.z);
    SELECTED_PART.scale.set(nsx, nsy, nsz);
    codeStatus('set to ' + tw.toFixed(2) + ' x ' + th.toFixed(2) + ' x ' + td.toFixed(2));
  }catch(e){ codeStatus('could not apply: ' + e.message); }
}
function setSelectMode(on){
  SELECT_MODE = on;
  codeStatus(on ? 'tap a part to select it' : 'selection off');
  if(!on){ try{ hideDimsPanel(); }catch(e){} }
  if(!on && SELECTED_PART){
    try{
      if(_origEmissive) SELECTED_PART.material.emissive.copy(_origEmissive);
      else SELECTED_PART.material.emissive.set(0x000000);
    }catch(e){}
    SELECTED_PART = null;
  }
}
function selectPart(obj){
  /* clear old */
  if(SELECTED_PART){
    try{
      if(_origEmissive) SELECTED_PART.material.emissive.copy(_origEmissive);
      else SELECTED_PART.material.emissive.set(0x000000);
    }catch(e){}
  }
  SELECTED_PART = obj;
  try{
    _origEmissive = obj.material.emissive ? obj.material.emissive.clone() : null;
    if(!obj.material.emissive) obj.material.emissive = new THREE.Color(0x000000);
    obj.material.emissive.set(0x336699);
    obj.material.emissiveIntensity = 0.8;
    obj.material.needsUpdate = true;
  }catch(e){}
  /* report + dimension inputs */
  var name = obj.name || 'unnamed';
  try{
    var box = new THREE.Box3().setFromObject(obj);
    var s = box.getSize(new THREE.Vector3());
    codeStatus('selected: ' + name);
    showDimsPanel(name, s.x, s.y, s.z);
  }catch(e){
    codeStatus('selected: ' + name);
  }
}
/* hook into the tap handler — if select mode is on, pick the part */
var _origTapHandler = null;
function enablePartSelection(){
  /* find the canvas tap — we add our own listener */
  try{
    var cv = document.querySelector('canvas');
    if(!cv || cv._selectHook) return;
    cv._selectHook = true;
    cv.addEventListener('click', function(e){
      if(!SELECT_MODE) return;
      try{
        var rect = cv.getBoundingClientRect();
        var mx = ((e.clientX - rect.left) / rect.width) * 2 - 1;
        var my = -((e.clientY - rect.top) / rect.height) * 2 + 1;
        var ray = new THREE.Raycaster();
        ray.setFromCamera({x: mx, y: my}, camera);
        var targets = [];
        if(DISSECT_GROUP) targets.push(DISSECT_GROUP);
        if(TABLE_BODIES) targets.push(TABLE_BODIES);
        if(PROVOLTA_MODEL) targets.push(PROVOLTA_MODEL);
        if(typeof stones!=='undefined') targets.push(stones);
        if(typeof RUBY_HANDLE