-
Lucio Zambon authored807d24f9
panther.js 69.06 KiB
// jshint esversion: 6
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import {MapControls} from 'three/addons/controls/MapControls.js';
import {EffectComposer} from 'three/addons/postprocessing/EffectComposer.js';
import {RenderPass} from 'three/addons/postprocessing/RenderPass.js';
import {OutlinePass} from 'three/addons/postprocessing/OutlinePass.js';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
// import { WebGPURenderer } from 'three/examples/jsm/renderers/WebGPURenderer.js';
/* *
import WebGPU from 'three/addons/capabilities/WebGPU.js';
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
/* */
import {ellipticaltubeGeometry} from 'components/ellipticaltube.js';
import {chamber} from 'components/chamber.js';
import {wall} from 'components/wall.js';
import * as componentCreator from 'bundle';
import {conf} from './panther_conf.js';
import * as blmres from './blm.js';
let firstUrl = true;
let historytime = + new Date();
function setUrl(name, value) {
let t = + new Date();
if (t - historytime < 200) return;
historytime = t;
parameters[name] = value;
const pp = [];
for (let i in parameters) {pp.push(i+'='+parameters[i]);}
const url = document.location.origin+document.location.pathname+'?'+pp.join('&').replace('=undefined', '');
if (firstUrl) window.history.pushState({"html":'panther.php',"pageTitle":'PAnTHer'},"", url);
else window.history.replaceState({"html":'panther.php',"pageTitle":'PAnTHer'},"", url);
firstUrl = false;
}
function compLink(event) {
window.open(document.getElementById("compdb").href, '_blank').focus();
event.stopPropagation();
return false;
}
const pa = document.location.search.replace('?','').split('&');
const parameters = {};
for (let i=0; i<pa.length; i++) {const p = pa[i].split('='); parameters[p[0]] = p[1];}
let cameraStarted = false;
const machineCaseSensitive = document.location.search.indexOf('machine=')>-1? document.location.search.split('machine=')[1].split('&')[0]: conf.default_machine;
const machine = machineCaseSensitive.toLowerCase();
let latticeFile = document.location.href.split('?')[0].split('/').slice(0,-1).join('/')+'/'+machine+'_lattice.json';
if (machine.indexOf('simulator_')>-1) {latticeFile = document.location.href.split('?')[0].split('/').slice(0,-1).join('/')+'/'+'simulator.php?lattice&machine='+machine.split('simulator_')[1];}
window.names = [];
window.alias = [];
let lattice;
let dipoleNum = 0;
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const component = [];
const facilities = [];
const status = [];
let selectedObjects = [];
let found = false;
const vlv = document.location.search.indexOf('vlv')>-1;
const vlvs = [];
const ps = document.location.search.indexOf('ps')>-1;
const demo = document.location.search.indexOf('demo')>-1; // || document.location.search.indexOf('machine=elettra2')>-1;
if (demo) document.getElementById('demo').style.display = 'block';
let camera, controls, scene, renderer;
let composer, outlinePass;
let tooltipObject = null;
let x1, y1, z1, x2, y2, z2, rx1, ry1, rz1, rx2, ry2, rz2, tang, alpha, d, den;
const envelopeMaterial = new THREE.MeshBasicMaterial({color: 0x00f0f0, transparent: true, opacity: 0.5, side: THREE.DoubleSide,});
let envelopeCounter = 0, envelopeIndex=0;
let envelopeSrc = '';
const envelopeMesh = [];
const envelopeSize = [];
const envelopeFactorX = 5000000;
const envelopeFactorY = 10000000;
const envelopeNum = 1286;
const envelopeOffset = 83;
const bpm = {radius: 100, srv: './bpm.php', mesh: {}};
const bpmMesh = {};
const bpmReferenceMesh = {};
const bpmPoints = {};
const bpmSkip = {};
const bpmIndex = {};
const bpmData = {};
const compData = {};
const compBuffer = {};
const loader = new GLTFLoader();
let fast = document.location.search.indexOf('fast')>-1? document.location.search.split('fast')[1].split('&')[0].split(','): false;
let premium = document.location.search.indexOf('premium')>-1? document.location.search.split('premium')[1].split('&')[0].split(','): false;
const real = document.location.search.indexOf('real')>-1 || (machine=='elettra2' && premium);
const blm = {oldIndex: null, reader: false, acqTime: null};
let latticenodes = 0;
const Ydefault = 100;
let params = {machine: machineCaseSensitive, search: '', envelopeNum: 0, backgroundColor: '#777777'};
let fel1 = false;
let fel2 = false;
if (machine.indexOf('fermi')>-1) {
fetch(conf.rchan+'srv-tango-srf-01:20000/f/access_control/safety/Undulator_access_state').then((response) => {return response.json();}).then((fel) => {
fel1 = fel[5] == 1;
fel2 = fel[6] == 1;
})
.catch(error => {console.log("Fetch error", error);});
}
const gui = new GUI();
gui.add(params, 'machine', conf.machineList).onChange(function() {toggleMachine(params.machine);});
params.mode = document.location.search.indexOf('fast')>-1? 'fast': document.location.search.indexOf('premium')>-1? 'premium': document.location.search.indexOf('plus')>-1? 'plus': 'normal';
gui.add(params, 'mode', conf.modes).onChange(function() {toggleParam('mode', params.mode);});
params.vlv = document.location.search.indexOf('vlv')>-1;
gui.add(params, 'vlv').name('vlv & bst').onChange(function() {toggleParam('vlv');});
params.ps = document.location.search.indexOf('ps')>-1;
gui.add(params, 'ps').onChange(function() {toggleParam('ps');});
gui.title('PAnTHer - controls');
gui.add(params, 'search'); // .onKeyup(function() {console.log('search');});
const sstring = $('.controller.string').children().eq(1).children().eq(0);
sstring.attr('id', 'sname');
sstring.attr('name', 'sname');
sstring.addClass("form-control sname");
gui.addColor(params, 'backgroundColor').onChange(function() {toggleParam('backgroundColor');});
params.far = typeof conf.default_far[machine]!='undefined'? conf.default_far[machine]: conf.default_far.default;
gui.add(params, 'far', 50, 500).onChange(function() {setFar(params.far);});
const debugcamera = document.location.search.indexOf('debugcamera')>-1;
if (debugcamera) $('body').append('<div class="debug" style="top: 0; position: fixed;"> x: <input size="5" id="x"/>, y: <input size="5" id="y"/>, z: <input size="5" id="z"/>, cx: <input size="5" id="cx"/>, cy: <input size="5" id="cy"/>, cz: <input size="5" id="cz"/>');
const highlight = document.location.search.indexOf('highlight=')==-1? []: document.location.search.split('highlight=')[1].split('&')[0].split(',');
params.highlightScale = document.location.search.indexOf('highlightScale=')==-1? (highlight.length? 0.5: 1): document.location.search.split('highlightScale=')[1].split('&')[0]-0;
params.highlightShrink = document.location.search.indexOf('highlightShrink=')==-1? 0.2: document.location.search.split('highlightShrink=')[1].split('&')[0]-0;
const stats = new Stats();
// window.stats = stats;
if (document.location.search.indexOf('stats')>-1) {
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild(stats.dom);
}
export function bpm_reference(action) {
if ($('.name:contains("bpmReference")') && $('.name:contains("bpmReference")').parent()) {
const name = $('.name:contains("bpmReference")').parent().select('.widget').children().eq(1).children('input').val();
if (action=='save') fetch(bpm.srv+'?save='+name+'&content='+JSON.stringify(bpmData)).then((response) => {return response.text();}).then((res) =>{alert(res=='OK'? 'BPM reference saved': res);}).catch(error => {console.log("Fetch error", error);});
else fetch(bpm.srv+'?open='+name).then((response) => {return response.json();}).then((res) =>{
for (let facility in res) {
// if (facility=='bts') use https://puma-01.elettra.eu/rchan.php?json&valueOnly&src=srv-tango-sre-01.ecs.elettra.trieste.it:20000/fb/bts/traj/SensorReferenceValues
bpmIndex[facility] = lattice[facility].bpm.offset;
const path = bpmPath(res[facility], facility);
const skiplen = lattice[facility].bpm.skip? lattice[facility].bpm.skip.length: 0;
const bpmGeometry = new THREE.TubeGeometry(path, lattice[facility].bpm.length-1-skiplen, bpm.radius, 16, (typeof lattice[facility].sections[0].chamber)!='undefined');
const material = new THREE.MeshBasicMaterial({color: 0x00ff80, transparent: true, opacity: 0.5,});
bpmReferenceMesh[facility] = new THREE.Mesh(bpmGeometry, material);
facilities[facility].add(bpmReferenceMesh[facility]);
}
})
.catch(error => {console.log("Fetch error", error);});
}
}
window.bpmreference = bpm_reference;
function updateBpm(data, facility) {
console.log('updateBpm(), data:', data, ', facility:', facility, ', bpmMesh', bpmMesh);
bpmData[facility] = data;
bpmMesh[facility].geometry.dispose();
bpmIndex[facility] = lattice[facility].bpm.offset;
const path = bpmPath(data, facility);
console.log('updateBpm(), data:', data, ', facility:', facility, ', bpmMesh', bpmMesh, 'path', path);
const len = lattice[facility].bpm.length-1-(typeof lattice[facility].bpm.skip=='undefined'? 0: lattice[facility].bpm.skip.length);
const bpmGeometry = new THREE.TubeGeometry(path, len, bpm.radius, 16, (typeof lattice[facility].sections[0].chamber)!='undefined');
bpmMesh[facility].geometry = bpmGeometry.clone();
bpmMesh[facility].geometry.computeVertexNormals();
bpmMesh[facility].geometry.normalsNeedUpdate = true;
bpmMesh[facility].geometry.verticesNeedUpdate = true;
bpmMesh[facility].geometry.dynamic = true;
data = null;
}
function readBpms() {
fetch(conf.bpmUrl)
.then((response) => {return response.json();})
.then((data) => {
for (let facility in data) {
updateBpm(data[facility], facility);
}
bpmTimes[bpmTimeIndex] = + new Date();
bpmTimeIndex = (bpmTimeIndex + 1) % bpmTimeLen;
if (bpmWait > 0) setTimeout(readBpms, bpmWait);
if (bpmTimes[bpmTimeIndex] > 0) {
const bpmfreq = 1000 / (+ new Date() - bpmTimes[bpmTimeIndex]) * bpmTimeLen;
$('#bpmfreq').val(bpmfreq.toPrecision(2) + ' fps');
// console.log(bpmfreq);
}
})
.catch(error => {console.log("Fetch error", error, conf.bpmUrl);});
}
function bpmPath(data, facility) {
let len = lattice[facility].bpm.length - bpmSkip[facility].length;
if (len>bpmPoints[facility].length) len = bpmPoints[facility].length;
// if (data!=-1) console.log('bpmDbg', len, facility, data, bpmPoints[facility].length);
const bpmCurve = [];
const bpmFactor = conf.bpmFactor[facility];
for (let i=0; i<len; i++) {
const j = (i) % len;
const tx = bpmPoints[facility][i][0].x + Math.cos(bpmPoints[facility][i][1])*(data.Hor? data.Hor[j]*1000*bpmFactor*params.bpmZoomH: 0);
const ty = Ydefault + bpmPoints[facility][i][0].y + (data.Ver? data.Ver[j]*1000*bpmFactor*params.bpmZoomV: 0);
const tz = bpmPoints[facility][i][0].z - Math.sin(bpmPoints[facility][i][1])*(data.Hor? data.Hor[j]*1000*bpmFactor*params.bpmZoomH: 0);
if (!isNaN(tx) && !isNaN(ty) && !isNaN(tz)) bpmCurve.push(new THREE.Vector3(tx, ty, tz));
}
// console.log(facility, bpmCurve);
return new THREE.CatmullRomCurve3(bpmCurve);
}
let bpmInited = false;
const bpmTimeLen = 3;
let bpmTimeIndex = 0;
const bpmTimes = new Array(bpmTimeLen).fill(0);
const bpmWait = document.location.search.indexOf('bpmWait=')>-1? document.location.search.split('bpmWait=')[1].split('&')[0]-0: 0;
function bpmAdd(facility) {
console.log('bpmDbg(), facility:', facility);
if (lattice[facility] && lattice[facility].bpm && (document.location.search.indexOf('&bpm')>-1 || document.location.search.indexOf('?bpm')>-1)) {
bpmIndex[facility] = lattice[facility].bpm.offset;
const path = bpmPath(-1, facility);
const skiplen = lattice[facility].bpm.skip? lattice[facility].bpm.skip.length: 0;
const bpmGeometry = new THREE.TubeGeometry(path, lattice[facility].bpm.length-1-skiplen, bpm.radius, 16, (typeof lattice[facility].sections[0].chamber)!='undefined');
const material = new THREE.MeshBasicMaterial({color: 0x8000ff, transparent: true, opacity: 0.5,});
bpmMesh[facility] = new THREE.Mesh(bpmGeometry, material);
facilities[facility].add(bpmMesh[facility]);
if (!bpmInited) {
bpmInited = true;
readBpms();
if (document.location.search.indexOf('bpm=')==-1 && bpmWait==0) setInterval(readBpms, 300);
bpmTimes[bpmTimeIndex] = + new Date();
bpmTimeIndex = (bpmTimeIndex + 1) % bpmTimeLen;
if (bpmWait > 0) setTimeout(readBpms, bpmWait);
}
}
console.log('bpmDbg(), bpmMesh:', bpmMesh, bpmMesh[facility]);
}
init();
export function setShrink(v) {console.log(params.highlightShrink, v); params.highlightShrink = v; toggleParam('highlightShrink', v); }
window.shrink = setShrink;
function mytoggle() {console.log('mytoggle()', this); toggleParam(this.property+(this.property.indexOf('highlight')==-1? '=hide': ''), this.value);} // https://lil-gui.georgealways.com/#Controller#onChange
function initIndex(lattice) {
const index = [];
for (let l in lattice.conf.index) {
let servicearea = false;
for (let i in lattice.servicearea.sections) {
for (let j in lattice.servicearea.sections[i].components) {
if (lattice.conf.index[l]==lattice.servicearea.sections[i].components[j].name) {servicearea = true;}
}
}
const cmd = 'window.findComponent('+"'"+lattice.conf.index[l]+"'"+', '+(servicearea? 'true': 'false')+')';
index.push('<button onclick="'+cmd+'">'+l+'</button>');
}
$('body').append('<div style="position: absolute; left: 5px; bottom: 5px;">'+index.join(' ')+'</div>');
}
function initShortcut(lattice) {
const index = [];
for (let l in lattice.conf.shortcut['3d']) {
const cmd = "document.location = document.location.pathname + '?"+lattice.conf.shortcut['3d'][l]+"';";
index.push('<button onclick="'+cmd+'" id="'+l.toLowerCase().split(' ').join('')+'">'+l+'</button>');
}
$('body').append('<div style="position: absolute; left: 5px; bottom: 5px;">'+index.join(' ')+'</div>');
if(document.location.search.indexOf('ps')>-1) $('#powersupplies').css('background-color', 'limegreen');
}
fetch(latticeFile).then((response) => {return response.json();}).then((flattice) => {
lattice = flattice;
const blmfacilities = [''];
const machineFolder = gui.addFolder('toggle facility');
if (Object.keys(lattice).length>1) {
const serviceareadefault = document.location.search.indexOf('servicearea')>-1 && document.location.search.indexOf('servicearea=')==-1;
for (let i in lattice) {
if (i=='conf') {
params[i] = false;
continue;
}
params[i] = document.location.search.indexOf(i+'=hide')==-1 && !(serviceareadefault && document.location.search.indexOf(i)==-1);
if (i=='servicearea' && document.location.search.indexOf(i)==-1) params[i] = false;
machineFolder.add(params, i).onChange(mytoggleFacility);
if (lattice[i].blm) {blmfacilities.push(i);}
// console.log('fetch()', latticeFile, i, params[i]);
if (lattice[i].bpm && (typeof params.bpm == 'undefined')) {
params.bpm = document.location.search.indexOf('&bpm')>-1 || document.location.search.indexOf('?bpm')>-1; gui.add(params, 'bpm').onChange(function() {toggleParam('bpm');});
if (params.bpm) {
$('.controller.boolean:last').append('<input id="bpmfreq" type="text" spellcheck="false" aria-labelledby="lil-gui-name-6">');
params.bpmZoomH = 1;
gui.add(params, 'bpmZoomH', 0.01, 100);
params.bpmZoomV = 1;
gui.add(params, 'bpmZoomV', 0.01, 100);
params.bpmReference = '2GeV';
gui.add(params, 'bpmReference');
/*if ($('.name:contains("bpmReference")') && $('.name:contains("bpmReference")').parent()) {
const ref = $('.name:contains("bpmReference")').parent().select('.widget').children().eq(1);
ref.append("<img src='./save.svg' style='height:20px; margin: 2px; cursor: pointer;' onClick='window.bpmreference(\"save\")'>");
ref.append("<img src='./open.svg' style='height:20px; margin: 2px; cursor: pointer;' onClick='window.bpmreference(\"open\")'>");
}*/
}
}
if (lattice[i].envelope) {envelopeMenu(params, gui, i);}
}
}
if (document.location.search.indexOf('servicearea')>-1 && document.location.search.indexOf('servicearea=')==-1) {
for (let i in lattice) {
if (document.location.search.indexOf(i)==-1);
}
}
machineFolder.close();
if (Object.keys(blmfacilities).length>1) {blmres.menu(lattice, blmfacilities, params, gui, blm, componentCreator);}
const highlightFolder = gui.addFolder('highlight components');
for (let j in componentCreator) {
if (j.indexOf('Update')>-1) continue;
const k = 'highlight_'+j;
params[k] = typeof highlight == 'object' && highlight.includes(j); highlightFolder.add(params, k).name(j).onChange(mytoggle);
}
highlightFolder.close();
if (document.location.search.indexOf('highlight=')>-1) {
gui.add(params, 'highlightScale', 0.1, 0.8, 0.01).onFinishChange(event => {toggleParam('highlightScale',event);});
}
$('#ui-id-1').css('z-index', 10000000);
if (document.location.search.indexOf('highlight=')>-1) {$('<div><iframe style="width: 100%;height:250px;" src="./spa/gauge.html?dark&r2only=1&r=100&ringwidth=58&max=1&throttlingPeriod=50&apply=shrink&val='+params.highlightShrink+'"></iframe></div>').insertBefore('.function');}
for (let i in lattice) {if (i!='conf') initLattice(lattice[i].sections, i);}
if (vlv) {
fetch(conf.vlvSrcUrl, {cache: "no-store"}).then((response) => {return response.text();}).then((data) => {
const vlvSrc = data.toUpperCase().substring(14).split(',');
for (let i=0; i<vlvs.length; i++) {
for (let j=0; j<vlvSrc.length; j++) {
const name = vlvSrc[j].split('/')[3];
if (vlvs[i].name.indexOf(name)>-1) {vlvs[i].vlvsrc = vlv; vlvs[i].vlvindex = j;}
// conf.bstmap.base
if (vlvSrc[j].split('/')[2]=="ACCESS_CONTROL" && i>39 && i<41) console.log('bst', j, vlvSrc[j], conf.bstmap.base, vlvs[i].name);
if (vlvSrc[j].indexOf('/')>-1 && vlvSrc[j].split('/')[2].indexOf(conf.bstmap.base)>-1) {
if (conf.bstmap[vlvs[i].name] && conf.bstmap[vlvs[i].name].indexOf(vlvSrc[j].split('/')[4])>-1) {vlvs[i].vlvsrc = 'bst'; vlvs[i].vlvindex = j;}
}
}
}
console.log('vlvSrc', data, vlvSrc, conf.bstmap, vlvs);
})
.catch(error => {console.log("Fetch error", error);});
setInterval(updateVlv, 1000);
}
if (document.location.search.indexOf('search=')>-1) findComponent(document.location.search.split('search=')[1].split('&')[0], false);
if (lattice.conf && lattice.conf.shortcut["3d"]) initShortcut(lattice);
if (lattice.conf && lattice.conf.index) initIndex(lattice);
console.log(lattice, facilities);
if (ps) {
fetch(conf.stateSrcUrl, {cache: "no-store"}).then((response) => {return response.text();}).then((data) => {
const statSrc = data.toUpperCase().split(',');
for (let j=0; j<statSrc.length; j++) {
if (typeof statSrc[j].split('/')[3] != 'undefined') statSrc[j] = statSrc[j].split('/')[3].replace('PS', '');
// if (statSrc[j].split('/')[1]=='POWER_SUPPLY') statSrc[j] = statSrc[j].split('/')[2].replace('PS', '');
}
for (let i=0; i<status.length; i++) {
for (let j=0; j<statSrc.length; j++) {
if (status[i].name.indexOf(statSrc[j])>-1) {status[i].statsrc = ps; status[i].statindex = j;}
}
}
console.log('statSrc', statSrc, status);
})
.catch(error => {console.log('stateSrcUrl: '+conf.stateSrcUrl, "Fetch error", error);});
setInterval(updateStatus, 1000);
}
if (lattice.conf && lattice.conf.modules) {
for (let i=0; i<lattice.conf.modules.length; i++) {
const mod = lattice.conf.modules[i];
import(mod).then((modInst) => {
console.log(modInst, mod);
modInst[mod](lattice, params, compData, gui, conf, facilities);
menuLink();
});
// window[lattice.conf.modules[i]](lattice, params, compData);
}
}
else menuLink();
})
.catch(error => {console.log("Fetch error", error);});
function menuLink() {
params.gotoAdmin = function() {document.location = './admin.php';};
gui.add(params, 'gotoAdmin').name('Admin');
params.goto2D = function() {document.location = './panther2d.php?machine='+params.machine;};
gui.add(params, 'goto2D').name('2D');
}
function showStatus(i, stat) {
status[i].scale.x = 1;
status[i].scale.y = 1;
status[i].scale.z = 1;
if (stat == 'null' || stat == '' || stat == 0 || (!fel1 && status[i].facility=='fel1') || (!fel2 && status[i].facility=='fel2')) {status[i].visible = false;}
else if (stat == 'ON' || stat == 'RUNNING') {
status[i].visible = true;
status[i].material.color.set(conf.stateLabelColor[stat]);
status[i].scale.x = statScale;
status[i].scale.y = statScale;
status[i].scale.z = statScale;
}
else {status[i].visible = true; status[i].material.color.set(conf.stateLabelColor[stat]);}
// if (i==40) console.log(i, status[i], stat);
}
function clearStatus() {
for (let i=0; i<status.length; i++) {
if (status[i].statsrc==ps && statVal[status[i].statindex]!=='ON' && statVal[status[i].statindex]!=='RUNNING') showStatus(i, 0);
}
}
let statVal;
const statScale = 0.07;
function updateStatus() {
fetch(conf.stateUrl, {cache: "no-store"}).then((response) => {return response.text();}).then((data) => {
// console.log('updateStatus()', fel1, fel2, status);
statVal = data.split(';');
for (let i=0; i<status.length; i++) {
if (status[i].statsrc==ps) {
if (status[i].statindex) showStatus(i, statVal[status[i].statindex]);
// if (status[i].name.indexOf('PSCH_B25')>-1) console.log(statVal, status, status[i].name, i, 'statindex: '+status[i].statindex, 'statVal: '+statVal[status[i].statindex]);
}
}
setTimeout(clearStatus, 600);
})
.catch(error => {console.log("Fetch error", error);});
}
for (let i=0; i<envelopeNum; i++) { envelopeSize[i] = 500;}
function updateEnvelopeCenters(data) {
for (let i=0; i<envelopeMesh.length; i++) {
x1 = (i+envelopeOffset) % envelopeNum;
x2 = (i+envelopeOffset+1) % envelopeNum;
const direction = false; //!envelopeMesh[i].mydirection;
const width = envelopeMesh[i].width;
envelopeMesh[i].geometry.dispose();
const envelopeGeometry = ellipticaltubeGeometry(envelopeFactorX*params.envelopeZoomX*data[0][direction? x2: x1],envelopeFactorY*params.envelopeZoomY*data[1][direction? x2: x1], envelopeFactorX*params.envelopeZoomX*data[0][direction? x1: x2],envelopeFactorY*params.envelopeZoomY*data[1][direction? x1: x2], width);
envelopeMesh[i].geometry = envelopeGeometry.clone();
envelopeMesh[i].geometry.computeVertexNormals();
envelopeMesh[i].geometry.normalsNeedUpdate = true;
envelopeMesh[i].geometry.verticesNeedUpdate = true;
envelopeMesh[i].geometry.dynamic = true;
}
data = null;
}
function envelopeMenu(params, gui, i) {
envelopeSrc = lattice[i].envelope.src;
params.envelope = document.location.search.indexOf('envelope')>-1;
gui.add(params, 'envelope').onChange(function() {toggleParam('envelope');});
if (document.location.search.indexOf('envelope')>-1) {
params.envelopeZoomX = 1;
gui.add(params, 'envelopeZoomX', 0.1, 10);
params.envelopeZoomY = 1;
gui.add(params, 'envelopeZoomY', 0.1, 10);
if (document.location.search.indexOf('envelope=debug')>-1) {params.envelopeDebug = 0; gui.add(params, 'envelopeDebug', 0, envelopeNum-1, 1);}
}
}
function readEnvelope() {
// console.log('envelopeSrc', envelopeSrc);
if (document.location.search.indexOf('envelope=debug')>-1) {
const arr = Array(envelopeNum).fill(0.00001); arr[params.envelopeDebug] = 0.00015;
updateEnvelopeCenters([arr, arr]);
}
else fetch(envelopeSrc)
.then((response) => {return response.json();})
.then((eventData) => {
updateEnvelopeCenters(eventData);
})
.catch(error => {console.log("Fetch error", error);});
}
if (document.location.search.indexOf('envelope')>-1) {
readEnvelope();
setInterval(readEnvelope, 1200);
}
function appendEnvelope(facility, i, distanceFromBendingCenter, width, size1, size2, tang, direction) {
if (tang==-Infinity) tang = -1e12;
if (tang==Infinity) tang = 1e12;
const x1 = i;
const z1 = i+1;
const d = distanceFromBendingCenter;
const den = Math.sqrt(tang*tang+1) * direction;
const envelopeGeometry = ellipticaltubeGeometry(size1,size2, size1,size2, width);
envelopeMesh[envelopeIndex] = new THREE.Mesh(envelopeGeometry, envelopeMaterial);
envelopeMesh[envelopeIndex].mydirection = direction>0;
envelopeMesh[envelopeIndex].width = width;
envelopeMesh[envelopeIndex].position.set(lattice[facility].sections[i].start.x + tang*d / den, Ydefault, lattice[facility].sections[i].start.z + d / den);
envelopeMesh[envelopeIndex].rotateY(Math.atan(tang));
facilities[facility].add(envelopeMesh[envelopeIndex]);
envelopeIndex++;
}
function envelopeAdd(flattice, i, j, facility, direction) {
if (document.location.search.indexOf('envelope')>-1 && flattice[j].bending && flattice[j].bending.lengthIndex && lattice[facility].envelope) {
let envelopePos = 0;
const stop = j==23? flattice[23].bending.lengthIndex + envelopeNum: flattice[j].bending.lengthIndex;
for (let e=flattice[(j+23)%24].bending.lengthIndex; e<stop; e++) {
let envelopeLen = lattice[facility].envelope.length[e % envelopeNum];
appendEnvelope(facility, i, envelopePos, envelopeLen*direction, envelopeSize[envelopeCounter+1], envelopeSize[envelopeCounter], tang, direction);
envelopeCounter++;
envelopePos += envelopeLen * (e==flattice[j].bending.lengthIndex? 0.5: 1);
}
}
}
// status
const normalMaterial = new THREE.MeshBasicMaterial({ color: 0xffff66});
const sphereGeometry = new THREE.SphereGeometry(500, 10, 10);
const rot = {x: $('#rotx').val(), y: $('#roty').val(), z: $('#rotz').val()};
let gltfscene;
function pushComponent(facility, i, m, tang, direction, y=Ydefault) {
let magnet = m>-1? lattice[facility].sections[i].components[m].type: 'dipole';
if (typeof compBuffer[magnet] == 'undefined') compBuffer[magnet] = [];
compBuffer[magnet].push({facility: facility, i: i, m: m, tang: tang, direction: direction, y: y});
}
function placemagnet(magnet, gs) {
const gltfscene = new THREE.Object3D();
gs.scale.set(900, 900, 900);
gs.rotateX(conf.real[magnet].rot.x);
gs.rotateY(conf.real[magnet].rot.y);
gs.rotateZ(conf.real[magnet].rot.z);
gs.position.set(conf.real[magnet].pos.x, conf.real[magnet].pos.y, conf.real[magnet].pos.z);
gltfscene.add(gs);
console.log('placemagnet', gltfscene, gltfscene.rotation, gltfscene.rotation.x, gltfscene.rotation.z);
for (let l=0; l<compBuffer[magnet].length; l++) {
const mycomp = gltfscene.clone();
const facility = compBuffer[magnet][l].facility;
const i = compBuffer[magnet][l].i;
const m = compBuffer[magnet][l].m;
const tang = compBuffer[magnet][l].tang;
const direction = compBuffer[magnet][l].direction;
const y = compBuffer[magnet][l].y;
const myname = m==-1? lattice[facility].sections[i].bending.name: lattice[facility].sections[i].components[m].name;
// console.log(facility, i, m, myname);
const id = extractId(myname);
const d = m==-1? 0: lattice[facility].sections[i].components[m].position;
const den = Math.sqrt(tang*tang+1) * direction;
const offset = m>-1 && lattice[facility].sections[i].components[m].offset3d? lattice[facility].sections[i].components[m].offset3d: [0, 0, 0];
mycomp.position.set(params.highlightScale*(lattice[facility].sections[i].start.x + tang*d / den) + offset[0], y + offset[1], params.highlightScale*(lattice[facility].sections[i].start.z + d / den + offset[2]));
if (mycomp.rotatedX) {mycomp.rotateZ(Math.atan(-tang));} else mycomp.rotateY(Math.atan(tang));
window.names.push(myname);
if (m>-1 && lattice[facility].sections[i].components[m].embedded) {
// console.log('mycomp.embedded',lattice[facility].sections[i].components[m].embedded);
for (let j=0; j<lattice[facility].sections[i].components[m].embedded.length; j++) {names.push(lattice[facility].sections[i].components[m].embedded[j]); alias.push([myname,lattice[facility].sections[i].components[m].embedded[j]]);}
}
if (id[1]) {window.names.push(id[1]); alias.push(id);}
mycomp.magnetType = magnet;
mycomp.name = myname;
if (direction>0) mycomp.rotateY(Math.PI);
compBuffer[magnet][l].name = mycomp.name;
if (m>-1 && lattice[facility].sections[i].components[m].href) mycomp.href = lattice[facility].sections[i].components[m].href;
if (highlight.length && !highlight.find(element => element == magnet)) {mycomp.scale.set(params.highlightShrink, params.highlightShrink, params.highlightShrink);}
facilities[facility].add(mycomp);
compBuffer[magnet][l].mycomp = mycomp;
scene.add(mycomp);
}
}
function loadreal(magnet) {
loader.load( './components/'+magnet+'.glb', function ( gltf ) {
placemagnet(magnet, gltf.scene);
}, undefined, function ( error ) {
console.error( error );
});
}
function appendComponent(facility, i, m, tang, direction, y=Ydefault) {
if (tang==-Infinity) tang = -1e12;
if (tang==Infinity) tang = 1e12;
let magnet = lattice[facility].sections[i].components[m].type;
// if (magnet=='quadrupole') return;
// if (glb.indexOf(magnet)>-1) return;
if (real && typeof conf.real[magnet] !== 'undefined') return;
if (componentCreator[magnet+'fast'] && (fast==false || fast.indexOf(magnet)>-1)) magnet = magnet+'fast';
if (componentCreator[magnet+'premium'] && (premium==false || premium.indexOf(magnet)>-1)) magnet = magnet+'premium';
if (componentCreator[magnet]) {
const myname = lattice[facility].sections[i].components[m].name;
const mycomp = componentCreator[magnet](lattice[facility].sections[i].components[m]);
mycomp.name = lattice[facility].sections[i].components[m].name;
const id = extractId(myname);
const d = lattice[facility].sections[i].components[m].position;
const den = Math.sqrt(tang*tang+1) * direction;
const offset = lattice[facility].sections[i].components[m].offset3d? lattice[facility].sections[i].components[m].offset3d: [0, 0, 0];
mycomp.position.set(params.highlightScale*(lattice[facility].sections[i].start.x + tang*d / den) + offset[0], y + offset[1], params.highlightScale*(lattice[facility].sections[i].start.z + d / den + offset[2]));
if (mycomp.rotatedX) {mycomp.rotateZ(Math.atan(-tang));} else mycomp.rotateY(Math.atan(tang));
window.names.push(lattice[facility].sections[i].components[m].name);
if (lattice[facility].sections[i].components[m].embedded) {
// console.log('mycomp.embedded',lattice[facility].sections[i].components[m].embedded);
for (let j=0; j<lattice[facility].sections[i].components[m].embedded.length; j++) {names.push(lattice[facility].sections[i].components[m].embedded[j]); alias.push([myname,lattice[facility].sections[i].components[m].embedded[j]]);}
}
if (id[1]) {window.names.push(id[1]); alias.push(id);}
mycomp.magnetType = magnet;
if (lattice[facility].sections[i].components[m].href) mycomp.href = lattice[facility].sections[i].components[m].href;
if (highlight.length && !highlight.find(element => element == magnet)) {mycomp.scale.set(params.highlightShrink, params.highlightShrink, params.highlightShrink);}
facilities[facility].add(mycomp);
if (magnet=='blm') {blmres.append(blm, facility, mycomp, direction);}
if (magnet=='vlv') {vlvs.push({'comp': mycomp, 'name': mycomp.name, 'type': magnet});}
if (magnet=='bst') {vlvs.push({'comp': mycomp, 'name': mycomp.name, 'type': magnet});}
if (Math.abs(den)>0.00001 && document.location.search.indexOf('nops')==-1) {
if (mycomp.name.indexOf('CHV_S')>-1) console.log(mycomp, i, m, lattice[facility].sections[i].components[m]);
if (typeof lattice[facility].sections[i].components[m].ps == "object") {
for (let pi=0; pi<lattice[facility].sections[i].components[m].ps.length; pi++) {
const sphere = new THREE.Mesh(sphereGeometry,normalMaterial.clone());
sphere.position.set(params.highlightScale*(lattice[facility].sections[i].start.x + tang*d / den) - 50 + 50*pi, y+500, params.highlightScale*(lattice[facility].sections[i].start.z + d / den));
sphere.name = lattice[facility].sections[i].components[m].ps[pi].replace('PS','') + '_status';
sphere.facility = facility;
sphere.visible = false;
status.push(sphere);
facilities[facility].add(sphere);
}
}
else {
const sphere = new THREE.Mesh(sphereGeometry,normalMaterial.clone());
sphere.position.set(params.highlightScale*(lattice[facility].sections[i].start.x + tang*d / den), y+500, params.highlightScale*(lattice[facility].sections[i].start.z + d / den));
sphere.name = mycomp.name+'_status';
sphere.facility = facility;
sphere.visible = false;
status.push(sphere);
facilities[facility].add(sphere);
}
}
return mycomp.position;
}
}
function extractId(name) {
if (name.indexOf('(')>-1) {
let tok = name.split('(');
return [tok[0].replaceAll('.','_').replaceAll(' ','_'), tok[1].replaceAll('.','_').replaceAll(' ','_').replaceAll(')','')];
}
return [name.replaceAll('.','_').replaceAll(' ','_'), false];
}
function initLattice(flattice, facility) {
// console.log('initLattice()', flattice, facility);
compData[facility] = [];
if (lattice[facility] && lattice[facility].bpm) {bpmPoints[facility] = []; bpmSkip[facility] = []; bpmData[facility] = []; }
console.log('compData', compData, facility);
facilities[facility] = new THREE.Object3D();
const wallColor = lattice.conf.wallColor? lattice.conf.wallColor: 'lightgray';
for (let i=0; i<flattice.length; i++) {
if (document.location.search.indexOf('maxlatticenodes=')>-1 && document.location.search.split('maxlatticenodes=')[1].split('&')[0]<latticenodes) break; else latticenodes++;
let j = (i+1) % flattice.length;
let k = (i+2) % flattice.length;
const y = typeof flattice[j].start.y == 'undefined'? Ydefault: flattice[j].start.y + Ydefault;
tang = (flattice[j].start.x-flattice[i].start.x)/(flattice[j].start.z-flattice[i].start.z);
const segmentLen = params.highlightScale*Math.sqrt((flattice[j].start.x-flattice[i].start.x)*(flattice[j].start.x-flattice[i].start.x) + (flattice[j].start.z-flattice[i].start.z)*(flattice[j].start.z-flattice[i].start.z));
if (flattice[j].chamber && (flattice[j].chamber.type=='chamber' || flattice[j].chamber.type=='wall')) {
const chamberMesh = flattice[j].chamber.type=='chamber'? chamber(segmentLen+15): wall(segmentLen+15, wallColor);
const yy = typeof flattice[i].start.y=='undefined' || typeof flattice[j].start.y=='undefined'? Ydefault: params.highlightScale*(flattice[j].start.y+flattice[i].start.y)/2;
// if (flattice[j].chamber.type=='wall') console.log(flattice[j].start, yy, Ydefault);
chamberMesh.position.set(params.highlightScale*(flattice[j].start.x+flattice[i].start.x)/2, yy, params.highlightScale*(flattice[j].start.z+flattice[i].start.z)/2);
if (chamberMesh.rotatedX) {chamberMesh.rotateZ(Math.atan(-tang));} else chamberMesh.rotateY(Math.atan(tang));
chamberMesh.scale.set(params.highlightScale, 1, params.highlightScale);
chamberMesh.magnetType = flattice[j].chamber.type;
facilities[facility].add(chamberMesh);
}
let bendingType = flattice[j].bending? flattice[j].bending.type: '';
if (componentCreator[bendingType+'fast'] && (fast==false || fast.indexOf(bendingType)>-1)) bendingType = bendingType+'fast';
if (componentCreator[bendingType+'premium'] && (premium==false || premium.indexOf(bendingType)>-1)) bendingType = bendingType+'premium';
const direction = (facility=='servicearea'? 1: 1)*((flattice[j].start.z<flattice[i].start.z /*!= flattice[j].start.x<=flattice[i].start.x*/)? -1: 1);
if (document.location.search.indexOf('components=hide')==-1 && flattice[j].bending && bendingType && typeof componentCreator[bendingType]!== 'undefined') {
if (real && typeof conf.real[bendingType] !== 'undefined') {
pushComponent(facility, i, -1, tang, direction, y);
}
else {
component[dipoleNum] = flattice[j].bending.length? componentCreator[bendingType](flattice[j].bending.length): componentCreator[bendingType]();
component[dipoleNum].position.set(params.highlightScale*flattice[j].start.x, y-params.highlightScale*100, params.highlightScale*flattice[j].start.z);
component[dipoleNum].name = flattice[j].bending.name;
const a1 = Math.atan2((flattice[j].start.z-flattice[i].start.z), (flattice[j].start.x-flattice[i].start.x));
const a2 = Math.atan2((flattice[k].start.z-flattice[j].start.z),(flattice[k].start.x-flattice[j].start.x));
alpha = (a1 + a2)/2;
if (a1>0 && a2<0) alpha += Math.PI;
component[dipoleNum].rotateY(3*Math.PI/2-alpha);
if (highlight.length && !highlight.find(element => (element == 'bending' || element == bendingType))) {component[dipoleNum].scale.set(params.highlightShrink, params.highlightShrink, params.highlightShrink);}
component[dipoleNum].magnetType = bendingType;
facilities[facility].add(component[dipoleNum]);
if (1 || flattice[j].bending.label && flattice[j].bending.label=='show') {
const div = document.createElement('div');
facilities[facility].add(component[dipoleNum]);
div.id = 'B' + dipoleNum; // 'B'+i; // flattice[j].bending.name; //
div.classList = [facility];
div.style.cssText = 'position: absolute; color: white;';
div.innerHTML = flattice[j].bending.name;
document.body.appendChild(div);
dipoleNum++;
}
}
window.names.push(flattice[j].bending.name);
}
if (document.location.search.indexOf('components=hide')==-1) if (flattice[i].components) for (let m=0; m<flattice[i].components.length; m++) {
// if (facility=='servicearea') console.log(facility, i, m, tang, direction);
const position = appendComponent(facility, i, m, tang, direction, y);
// console.log(position, compData, facility);
// if (position) compData[facility].push([position.clone(), Math.atan(tang), direction]);
if (position) compData[facility].push([position.clone(), Math.atan2(flattice[j].start.x-flattice[i].start.x, flattice[j].start.z-flattice[i].start.z), direction]);
pushComponent(facility, i, m, tang, direction, y);
if (flattice[i].components[m].type=='bpm' && (document.location.search.indexOf('&bpm')>-1 || document.location.search.indexOf('?bpm')>-1) && lattice[facility].bpm) {
if (lattice[facility].bpm.skip && (lattice[facility].bpm.skip.indexOf(flattice[i].components[m].name)>-1)) {bpmSkip[facility].push(bpmPoints[facility].length);}
else bpmPoints[facility].push([position.clone(), Math.atan(tang), direction]);
}
}
envelopeAdd(flattice, i, j, facility, direction);
}
// console.log('compBuffer', compBuffer);
if (real) for (let i in conf.real) {loadreal(i);}
// console.log('vlvs', vlvs); console.log('facilities', facilities, 'names', window.names.join(';'));
bpmAdd(facility);
facilities[facility].name = facility;
scene.add(facilities[facility]);
// console.log('mytoggleFacility()', facility, params[facility], 'names', names);
if (!params[facility]) mytoggleFacility(false, facility);
}
function updateVlv() {
fetch(conf.vlvUrl, {cache: "no-store"}).then((response) => {return response.text();}).then((data) => {
const vlvVal = data.split(';');
vlvVal[0] = vlvVal[0].split(':')[1];
// console.log(conf.vlvUrl, vlvVal, vlvs);
for (let i=0; i<vlvs.length; i++) {
if (vlvs[i].type=='vlv') componentCreator.vlvUpdate(vlvs[i].comp, vlvVal[vlvs[i].vlvindex]=='CLOSED'? 'red': (vlvVal[vlvs[i].vlvindex]=='OPENED'? 'limegreen': 'grey'));
if (vlvs[i].type=='bst') {
if (typeof vlvVal[vlvs[i].vlvindex] != 'string') continue;
const val = vlvVal[vlvs[i].vlvindex].split(',');
componentCreator.bstUpdate(vlvs[i].comp, val[0]=='true'? 'limegreen': (val[1]=='true'? 'red': 'grey'));
}
}
})
.catch(error => {console.log("Fetch error", error);});
}
let cameraStep=0;
let cameraUpdate = null;
let focusPos = new THREE.Vector3();
const numSteps = 15;
function stepCamera(x, y, z, rx, ry, rz) {
if (debugcamera) {$('#x').val(x); $('#y').val(y); $('#z').val(z); $('#cx').val(rx); $('#cy').val(ry); $('#cz').val(rz);}
camera.position.set(x, y, z);
camera.lookAt(rx, ry, rz);
// controls.target.set(x, y, z);
// scene.rotation.set(rx, ry, rz);
// camera.updateProjectionMatrix();
renderer.render(scene, camera);
// composer.render();
}
function updateCameraMatrix() {
camera.updateProjectionMatrix();
scene.updateMatrixWorld();
console.log('updateCameraMatrix()', camera);
}
function updateCamera() {
cameraStep = cameraStep>3? cameraStep-1: (cameraStep>1? cameraStep-0.5: cameraStep-0.25);
stepCamera(x2-(x2-x1)*cameraStep/numSteps, y2-(y2-y1)*cameraStep/numSteps, z2-(z2-z1)*cameraStep/numSteps, rx2-(rx2-rx1)*cameraStep/numSteps, ry2-(ry2-ry1)*cameraStep/numSteps, rz2-(rz2-rz1)*cameraStep/numSteps);
// console.log('updateCamera()', cameraStep, numSteps, camera.rotation, camera);
if (cameraStep<=0) {clearInterval(cameraUpdate); cameraUpdate = null; setTimeout(updateCameraMatrix, 2000);}
}
export function findComponent(name, outline=true) {
if (cameraStep>0) return;
if (name==null) name = document.getElementById('sname').value;
for (let i=0; i<alias.length; i++) if (alias[i][1]==name) name = alias[i][0];
document.getElementById('sname').value = name;
outlinePass.selectedObjects = [];
for (let facility in scene.children) {
for (let comp in scene.children[facility].children) {
if (scene.children[facility].children[comp].name==name) {
const dist = scene.children[facility].name=='servicearea'? 7000: 1000;
facilities[scene.children[facility].name].visible = true; // make facility visible
// transverse lil menu and check facility checkbox
const menu = $('.allow-touch-styles').children();
for (let i=0; i<menu.length; i++) {
if (menu[i].innerHTML=='toggle facility') {
// $('.allow-touch-styles').children().eq(3).children().eq(4).children().eq(1).children().attr('checked', true)
const fac = menu.eq(i+1).children();
for (let j=0; j<fac.length; j++) {
if (fac.eq(j).children()[0].innerHTML==scene.children[facility].name) {
fac.eq(j).children().eq(1).children().attr('checked', true);
break;
}
}
break;
}
}
found = true;
const selectedObject = scene.children[facility].children[comp];
if (outline) {outlinePass.selectedObjects = [selectedObject]; addSelectedObject(selectedObject);}
renderer.render(scene, camera);
x1 = camera.position.x;
y1 = camera.position.y;
z1 = camera.position.z;
x2 = scene.children[facility].children[comp].position.x+dist*Math.sign(scene.children[facility].children[comp].position.x);
y2 = scene.children[facility].children[comp].position.y+dist*Math.sign(scene.children[facility].children[comp].position.y);
z2 = scene.children[facility].children[comp].position.z+dist*Math.sign(scene.children[facility].children[comp].position.z);
console.log("findComponent()", name, selectedObject, x1, x2, scene.children[facility].children[comp].position, scene.children[facility]);
const lookat = (new THREE.Vector3(0, 0, -20000)).applyQuaternion(camera.quaternion).add(camera.position); // https://stackoverflow.com/questions/27957645/three-js-find-the-current-lookat-of-a-camera
rx1 = lookat.x;
ry1 = lookat.y;
rz1 = lookat.z;
rx2 = scene.children[facility].children[comp].position.x;
ry2 = scene.children[facility].children[comp].position.y;
rz2 = scene.children[facility].children[comp].position.z;
if (debugcamera) {$('#x').val(x1); $('#y').val(y1); $('#z').val(z1); $('#cx').val(rx1); $('#cy').val(ry1); $('#cz').val(rz1);}
cameraStep = numSteps;
updateCamera();
if (cameraUpdate == null) cameraUpdate = setInterval(updateCamera, 100);
}
}
}
}
window.findComponent = findComponent;
$(function() {$(".sname").autocomplete({source: names, close: function(event, ui) {findComponent($('#sname').val()); }, select: function(event, ui) {findComponent(ui.item.value); return false;}});});
function addSelectedObject(object) {
// console.log("addSelectedObject()", object, outlinePass.selectedObjects);
selectedObjects = [];
selectedObjects.push(object);
}
function checkOutline(selectedObject) {
raycaster.setFromCamera(pointer, camera);
addSelectedObject(selectedObject);
outlinePass.selectedObjects = selectedObjects;
}
let popupOn = false;
if (navigator.userAgent.indexOf('Firefox/63')==-1) document.onclick = handleMouseMove;
if (navigator.userAgent.indexOf('Firefox/63')>-1 && document.location.search.indexOf('popup')>-1) document.addEventListener('click', handleClick);
function handleClick(event) {
// document.removeEventListener('click', handleClick);
console.log(event);
let j=1;
const debug = document.location.search.indexOf('debug=')>-1? document.location.search.split('debug=')[1].split('&')[0].split(','): '';
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), event',event);
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - ((28+event.clientY) / (window.innerHeight)) * 2 + 1;
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), pointer',pointer);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), intersects',intersects);
window.tooltipObject = tooltipObject = null;
document.getElementById('tooltipFrame').src = document.getElementById('compdb').href = '';
document.getElementById('compdb').style.display = 'inline';
if (document.location.pathname.indexOf('ff63')==-1) document.getElementById('tooltip').style.display = 'none';
document.getElementById('tooltipFrame').width = '500px';
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), tooltipObject',tooltipObject);
if (!found) outlinePass.selectedObjects = selectedObjects = [];
found = false;
if (typeof intersects[0] == 'undefined') return;
let selectedObject = intersects[0].object.parent;
for (let i=intersects.length-1; i>=0; i--) {
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), i',i);
let visible = true;
let tobj = intersects[i].object;
// https://discourse.threejs.org/t/how-to-check-if-mesh-ancestor-is-visible-in-canvas/42494/5
while (tobj !== scene) {
if (tobj.visible == false) visible = false;
tobj = tobj.parent;
}
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), tobj',tobj);
if (visible == false) continue;
// console.log("handleMouseMove()", event, intersects[i].object);
if (intersects[i].object.parent.name.length==0 && intersects[i].object.parent.parent.name.length==0) continue;
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), intersects[i].object',intersects[i].object);
if (intersects[i].object.parent.href) {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = demo? './magnetdemo.html?'+intersects[i].object.parent.href.split('?')[1]: intersects[i].object.parent.href;
document.getElementById('compdb').style.display = 'none';
}
else if (intersects[i].object.href) {
window.tooltipObject = tooltipObject = intersects[i].object;
document.getElementById('tooltipFrame').src = demo? './magnetdemo.html?'+intersects[i].object.href.split('?')[1]: intersects[i].object.href;
document.getElementById('compdb').href = conf.compdb + intersects[i].object.name;
document.getElementById('compname').innerHTML = intersects[i].object.name;
}
else if (intersects[i].object.magnetType) {
window.tooltipObject = tooltipObject = intersects[i].object;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s='+(demo? 'knobdemo': intersects[i].object.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.name;
document.getElementById('compname').innerHTML = intersects[i].object.name;
}
else if (intersects[i].object.parent.magnetType) {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s='+(demo? 'knobdemo': intersects[i].object.parent.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.name;
}
else if (intersects[i].object.parent.parent.magnetType) {
document.getElementById('tooltipFrame').height = '320px';
window.tooltipObject = tooltipObject = intersects[i].object.parent.parent;
selectedObject = intersects[i].object.parent.parent;
document.getElementById('tooltipFrame').src = conf.tooltipAppNormal+'?s='+(demo? 'knobdemo': intersects[i].object.parent.parent.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.parent.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.parent.name;
}
else {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s=label¶m='+intersects[i].object.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.name;
}
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), src',document.getElementById('tooltipFrame').src, ', href',document.getElementById('compdb').href);
const name = document.getElementById('compname').innerHTML;
document.getElementById('compdb').removeAttribute("disabled");
if (name.indexOf('_B')>-1 || name.indexOf('_P')>-1 || (name.indexOf('_S')>-1 && name.indexOf('_SFEL')==-1 && name.indexOf('_SP')==-1 && name.indexOf('_SC')==-1)) document.getElementById('compdb').setAttribute("disabled", true);
document.getElementById('compname').innerHTML = document.getElementById('compname').innerHTML.replace('RTBPM','BPM');
document.getElementById('compdb').href = document.getElementById('compdb').href.replace('RTBPM','BPM').split(' ')[0].split('%')[0];
if (document.getElementById('tooltipFrame').src.indexOf('?s=beamline')>-1) {
document.getElementById('compdb').href = 'http://adam.elettra.trieste.it/projects/blcs/beamwatch/';
document.getElementById('compname').innerHTML = document.getElementById('compname').innerHTML + ' in ADAM Beamwatch';
}
document.getElementById('tooltipFrame').src.replace('s=dipolefermi', 's=bending');
}
if (document.getElementById('tooltipFrame').src.indexOf('s=wall')>-1) {document.getElementById('compdb').style.display = 'none'; return;} // avoid walls
console.log('handleClick()', intersects, document.getElementById('tooltipFrame').src, selectedObject);
// if (selectedObject.parent && selectedObject.parent.type && selectedObject.parent.type == "Scene") return; // avoid selecting everything
checkOutline(selectedObject);
renderer.render(scene, camera);
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), compdb',document.getElementById("compdb"));
document.getElementById("compdb").addEventListener("click", compLink); // https://j11y.io/javascript/debug-jquery-events-with-listhandlers/
}
function handleMouseMove(event) {
let j=1;
const debug = document.location.search.indexOf('debug=')>-1? document.location.search.split('debug=')[1].split('&')[0].split(','): '';
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), event',event);
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - ((28+event.clientY) / (window.innerHeight)) * 2 + 1;
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), pointer',pointer);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), intersects',intersects);
window.tooltipObject = tooltipObject = null;
document.getElementById('tooltipFrame').src = document.getElementById('compdb').href = '';
document.getElementById('compdb').style.display = 'inline';
if (document.location.pathname.indexOf('ff63')==-1) document.getElementById('tooltip').style.display = 'none';
document.getElementById('tooltipFrame').width = '500px';
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), tooltipObject',tooltipObject);
if (!found) outlinePass.selectedObjects = selectedObjects = [];
found = false;
if (typeof intersects[0] == 'undefined') return;
let selectedObject = intersects[0].object.parent;
for (let i=intersects.length-1; i>=0; i--) {
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), i',i);
let visible = true;
let tobj = intersects[i].object;
// https://discourse.threejs.org/t/how-to-check-if-mesh-ancestor-is-visible-in-canvas/42494/5
while (tobj !== scene) {
if (tobj.visible == false) visible = false;
tobj = tobj.parent;
}
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), tobj',tobj);
if (visible == false) continue;
// console.log("handleMouseMove()", event, intersects[i].object);
if (intersects[i].object.parent.name.length==0 && intersects[i].object.parent.parent.name.length==0) continue;
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), intersects[i].object',intersects[i].object);
if (intersects[i].object.parent.href) {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = demo? './magnetdemo.html?'+intersects[i].object.parent.href.split('?')[1]: intersects[i].object.parent.href;
document.getElementById('compdb').style.display = 'none';
}
else if (intersects[i].object.href) {
window.tooltipObject = tooltipObject = intersects[i].object;
document.getElementById('tooltipFrame').src = demo? './magnetdemo.html?'+intersects[i].object.href.split('?')[1]: intersects[i].object.href;
document.getElementById('compdb').href = conf.compdb + intersects[i].object.name;
document.getElementById('compname').innerHTML = intersects[i].object.name;
}
else if (intersects[i].object.magnetType) {
window.tooltipObject = tooltipObject = intersects[i].object;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s='+(demo? 'knobdemo': intersects[i].object.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.name;
document.getElementById('compname').innerHTML = intersects[i].object.name;
}
else if (intersects[i].object.parent.magnetType) {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s='+(demo? 'knobdemo': intersects[i].object.parent.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.name;
}
else if (intersects[i].object.parent.parent.magnetType) {
document.getElementById('tooltipFrame').height = '320px';
window.tooltipObject = tooltipObject = intersects[i].object.parent.parent;
selectedObject = intersects[i].object.parent.parent;
document.getElementById('tooltipFrame').src = conf.tooltipAppNormal+'?s='+(demo? 'knobdemo': intersects[i].object.parent.parent.magnetType.replace('fast', '').replace('premium', ''))+'¶m='+intersects[i].object.parent.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.parent.name;
}
else {
window.tooltipObject = tooltipObject = intersects[i].object.parent;
document.getElementById('tooltipFrame').src = conf.tooltipApp+'?s=label¶m='+intersects[i].object.parent.name;
document.getElementById('compdb').href = conf.compdb+intersects[i].object.parent.name;
document.getElementById('compname').innerHTML = intersects[i].object.parent.name;
}
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), src',document.getElementById('tooltipFrame').src, ', href',document.getElementById('compdb').href);
const name = document.getElementById('compname').innerHTML;
document.getElementById('compdb').removeAttribute("disabled");
if (name.indexOf('_B')>-1 || name.indexOf('_P')>-1 || (name.indexOf('_S')>-1 && name.indexOf('_SFEL')==-1 && name.indexOf('_SP')==-1 && name.indexOf('_SC')==-1)) document.getElementById('compdb').setAttribute("disabled", true);
document.getElementById('compname').innerHTML = document.getElementById('compname').innerHTML.replace('RTBPM','BPM');
document.getElementById('compdb').href = document.getElementById('compdb').href.replace('RTBPM','BPM').split(' ')[0].split('%')[0];
if (document.getElementById('tooltipFrame').src.indexOf('?s=beamline')>-1) {
document.getElementById('compdb').href = 'http://adam.elettra.trieste.it/projects/blcs/beamwatch/';
document.getElementById('compname').innerHTML = document.getElementById('compname').innerHTML + ' in ADAM Beamwatch';
}
document.getElementById('tooltipFrame').src.replace('s=dipolefermi', 's=bending');
}
console.log('handleMouseMove()', intersects, document.getElementById('tooltipFrame').src);
if (document.getElementById('tooltipFrame').src.indexOf('s=wall')>-1) {window.tooltipObject = tooltipObject = null; return;} // avoid walls
console.log('handleMouseMove()', intersects, document.getElementById('tooltipFrame').src);
if (selectedObject.parent && selectedObject.parent.type && selectedObject.parent.type == "Scene") return; // avoid selecting everything
checkOutline(selectedObject);
renderer.render(scene, camera);
if (debug.indexOf(''+j++)>-1) console.log('handleMouseMove(), compdb',document.getElementById("compdb"));
document.getElementById("compdb").addEventListener("click", compLink); // https://j11y.io/javascript/debug-jquery-events-with-listhandlers/
}
animate();
function moveCamera() {
const cameraInitPosition = typeof conf.default_camera_init[machine] == 'undefined'? conf.default_camera_init.default: conf.default_camera_init[machine];
// const cameraPos = (document.location.search.indexOf('camera=')>-1)? document.location.search.split('camera=')[1].split('&')[0].split(','): [-22000, 7200, 70000, -0.1, -0.3, -0.03];
const cameraPos = (document.location.search.indexOf('camera=')>-1)? document.location.search.split('camera=')[1].split('&')[0].split(','): cameraInitPosition;
if (debugcamera) {$('#x').val(camera.position.x); $('#y').val(camera.position.y); $('#z').val(camera.position.z); $('#cx').val(camera.rotation.x); $('#cy').val(camera.rotation.y); $('#cz').val(camera.rotation.z);}
if (cameraStarted) {if (debugcamera) setUrl('camera', camera.position.x+','+camera.position.y+','+camera.position.z+','+camera.rotation.x+','+camera.rotation.y+','+camera.rotation.z); return;}
console.log('moveCamera()', camera.rotation, machine, cameraInitPosition);
cameraStarted = true;
camera.position.set(cameraPos[0]*params.highlightScale, cameraPos[1]*params.highlightScale, cameraPos[2]*params.highlightScale);
camera.rotation.set(cameraPos[3],cameraPos[4],cameraPos[5]);
}
function setFar(far) {
console.log('setFar', far);
camera.far = far*1000;
camera.updateProjectionMatrix();
}
window.setFar = setFar;
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(document.location.search.indexOf('background=')>-1? document.location.search.split('background=')[1].split('&')[0]: 0x777777);
const canvas = document.querySelector('#c');
renderer = new THREE.WebGLRenderer({antialias: false, canvas, logarithmicDepthBuffer: true});
// renderer = new WebGPURenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const fov = document.location.search.indexOf('fov=')>-1? document.location.search.split('fov=')[1].split('&')[0]-0: 45;
camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 0.001, fast!==false? 1000000: 120000);
// controls
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) controls = new MapControls(camera, renderer.domElement);
else {
controls = new OrbitControls(camera, renderer.domElement);
controls.mouseButtons.LEFT = THREE.MOUSE.PAN;
controls.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
controls.keyPanSpeed = 1000;
}
console.log('controls', controls);
// https://threejs.org/docs/#examples/en/controls/OrbitControls
// https://stackoverflow.com/questions/61519164/three-js-orbitcontrols-panning-and-scene-rotation?rq=3
// https://github.com/pmndrs/drei/issues/186
// controls.enableDamping = true;
controls.enableDamping = false; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.1;
controls.screenSpacePanning = false;
controls.minDistance = 0.01;
controls.maxDistance = 300000;
controls.maxPolarAngle = Math.PI / 2;
// controls.update();
window.addEventListener('resize', onWindowResize);
camera.updateMatrixWorld(true);
setFar(params.far);
const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
dirLight1.position.set(1, 1, 1);
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0x888888, 3);
dirLight2.position.set(-1, -1, -1);
scene.add(dirLight2);
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);
// postprocessing (derived from https://github.com/mrdoob/three.js/blob/master/examples/webgl_postprocessing_outline.html)
composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
outlinePass.edgeStrength = 100;
outlinePass.edgeGlow = 0.2;
outlinePass.edgeThickness = 6;
outlinePass.pulsePeriod = 2;
outlinePass.visibleEdgeColor.set('#700080');
outlinePass.hiddenEdgeColor.set('#400050');
composer.addPass(outlinePass);
console.log('scene', scene, 'controls', controls, 'camera', camera);
if (debugcamera) {
// axis grid
const redmaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });
const redgeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(-3000, 0, 0), new THREE.Vector3(3000, 0, 0)]);
const redline = new THREE.Line( redgeometry, redmaterial );
scene.add(redline);
for (let k=-30; k<=30; k++) {
const c = redline.clone();
c.position.set(0, k*100, 0);
scene.add(c);
}
const greenmaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
const greengeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, -3000, 0), new THREE.Vector3(0, 3000, 0)]);
const greenline = new THREE.Line( greengeometry, greenmaterial );
scene.add(greenline);
for (let k=-30; k<=30; k++) {
const c = greenline.clone();
c.position.set(0, 0, k*100);
scene.add(c);
}
const bluematerial = new THREE.LineBasicMaterial({ color: 0x0000ff });
const bluegeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, -3000), new THREE.Vector3(0, 0, 3000)]);
const blueline = new THREE.Line( bluegeometry, bluematerial );
scene.add(blueline);
for (let k=-30; k<=30; k++) {
const c = blueline.clone();
c.position.set(k*100, 0, 0);
scene.add(c);
}
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
if (document.location.search.indexOf('stat')>-1) stats.begin();
moveCamera();
// controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
render();
composer.render();
if (document.location.search.indexOf('stat')>-1) stats.end();
}
function render() {
const width = window.innerWidth, height = window.innerHeight;
const widthHalf = width / 2, heightHalf = height / 2;
if (dipoleNum>0) renderer.render(scene, camera);
for (let dipoleIndex=0; dipoleIndex<dipoleNum; dipoleIndex++) if (typeof component[dipoleIndex] != 'undefined' && document.getElementById('B'+(dipoleIndex))) {
const pos = component[dipoleIndex].position.clone();
pos.project(camera);
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
const dist = Math.abs(camera.position.x-component[dipoleIndex].position.x)+Math.abs(camera.position.y-component[dipoleIndex].position.y)+Math.abs(camera.position.z-component[dipoleIndex].position.z);
const facility = document.getElementById('B'+(dipoleIndex)).className;
document.getElementById('B'+(dipoleIndex)).style.display = (frustum.containsPoint(component[dipoleIndex].position) && facilities[facility].visible && dist>140 && dist<40000*params.highlightScale)? 'block': 'none';
document.getElementById('B'+(dipoleIndex)).style.top = (-(pos.y * heightHalf) + heightHalf - 20)+'px';
document.getElementById('B'+(dipoleIndex)).style.left = ((pos.x * widthHalf ) + widthHalf + 15)+'px';
}
blmres.render(blm, params, componentCreator, camera, THREE);
if (typeof tooltipObject !== 'undefined' && tooltipObject != null) {
let pos2 = window.tooltipObject.position.clone();
pos2.project(camera);
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
let top = (-(pos2.y * heightHalf) + heightHalf );
if (window.innerHeight - top < 400) top = window.innerHeight - 400;
if (document.location.pathname.indexOf('ff63')==-1) {
document.getElementById('tooltip').style.top = top+'px';
document.getElementById('tooltip').style.left = ((pos2.x * widthHalf ) + widthHalf + 25)+'px';
document.getElementById('tooltip').style.display = (frustum.containsPoint(tooltipObject.position))? 'block': 'none';
}
}
}
function updateUrl(param, value) {
let url = document.location.href;
if (url.indexOf(param)==-1) url = url + (url.indexOf('?')==-1? '?': '&') + param + '=' + value;
else {
const a = url.split(param+'=');
const b = a[1].split('&');
url = a[0] + param + '=' + value + (b.length>1? '&' + b[1]: '');
}
window.history.pushState({"html":'panther.php',"pageTitle":'PAnTHer'},"", url);
}
function mytoggleFacility(value, facility=false) {
if (!facility) facility = this.property;
console.log('facilities', facilities, facility);
facilities[facility].visible = value;
}
export function toggleMachine(machine) {
if (document.location.search.indexOf('machine=')==-1) document.location = document.location.href + (document.location.href.indexOf('?')==-1? '?': '&') + 'machine='+machine;
let search = document.location.search.split('machine=')[1].split('&')[0];
document.location = document.location.href.replace('machine='+search, 'machine='+machine);
}
export function toggleParam(name, value) {
if (name=='backgroundColor') {scene.background = new THREE.Color(params.backgroundColor);return;}
const urlparam = {};
let search = document.location.search.replace('?', '').split('&');
if (search[0]=='') search.splice(0,1);
for (let i=0; i<search.length; i++) {const s = search[i].split('='); urlparam[s[0]] = s.length>1? s[1]: undefined;}
if (name.indexOf('highlight_')>-1) {
const hname = name.split('ighlight_')[1];
if (!highlight.includes(hname)) highlight.push(hname); else highlight.splice(highlight.indexOf(hname), 1);
urlparam.highlight = highlight.join(',');
search=[];
for (let i in urlparam) search.push(i+(typeof urlparam[i]!='undefined'? '='+urlparam[i]: ''));
}
else if (name.indexOf('highlight')>-1) {
if (name=='highlightShrink') {
updateUrl(name, params.highlightShrink);
for (let i=0; i<scene.children.length; i++) {
for (let j=0; j<scene.children[i].children.length; j++) {
if (!highlight.find(element => element == scene.children[i].children[j].magnetType))
if (scene.children[i].children[j].magnetType != 'chamber') scene.children[i].children[j].scale.set(params.highlightShrink, params.highlightShrink, params.highlightShrink);
}
}
return;
}
urlparam[name] =value;
search=[];
for (let i in urlparam) search.push(i+(typeof urlparam[i]!='undefined'? '='+urlparam[i]: ''));
}
else if (search.includes(name)) {
search.splice(search.indexOf(name), 1);
}
else {
search.push(name);
}
if (search.indexOf("highlight=")>-1) {for (let j=search.length-1; j>=0; j--) if (search[j].indexOf("highlight")>-1) search.splice(j, 1);}
if (name=='mode') {
if (search.indexOf(value)>-1 || (value=='normal' && search.indexOf('fast')==-1 && search.indexOf('premium')==-1 && search.indexOf('plus')==-1)) return;
search = search.filter(item => item !== 'fast' && item !== 'plus' && item !== 'premium' && item !== 'mode');
if (value!='normal') search.push(value);
}
const res = search.join('&');
document.location = document.location.href.split('?')[0]+(res.length? '?'+res: '');
}
window.mytoggle = toggleParam;
if (demo) {
$('.lil-gui').css({'top': '120px', 'right': '1px'});
$( "body" ).append('<div class="lil-gui allow-touch-styles root autoPlace" style="top: 0px;right: 1px;height: 120px;background-color: black;"></div>');
}
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) gui.close();