/** * Convert text in the preview text editor to js object * * @return [string, object] full mvconfig text string and mvconfig object */ extractMvconfig = function() { const editText = $('#wpTextbox1').val() const extractConfig = editText.match(/([\S\s]*?)<\/mvconfig>/) let mvconfig = (extractConfig.length >= 2) ? JSON.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}} if (mvconfig.viewerConfig === undefined) { mvconfig.viewerConfig = { default: { "camera-controls": true } } } if (mvconfig.annotations === undefined) { mvconfig.annotations = {} } if (mvconfig.annotationSets === undefined) { mvconfig.annotationSets = {} } return [editText, mvconfig] } /** * Reads the json string in the edit panel * and updates hotspot elements and menu settings * * @return {bool|object} arrays of view and set names on successful read and update false on failure */ readMvconfig = function() { let hotspotsObj = [] let mvconfig let slotNum = 1 createHotspot = function(hsLabel, hsSlot, hsTag) { let newHs = document.createElement('button') newHs.classList.add('Hotspot') newHs.setAttribute('slot',`hotspot-${hsSlot}`) newHs.setAttribute('ontouchstart', 'event.stopPropagation()') newHs.setAttribute('onclick', 'onAnnotation(event)') newHs.setAttribute('onmousedown', 'grabAnnotation(event)') newHs.setAttribute('onmouseup', 'releaseAnnotation(event)') Object.keys(mvconfig.annotations[hsLabel]).forEach((prop) => { newHs.setAttribute(prop, mvconfig.annotations[hsLabel][prop]) }) let newAn = document.createElement('div') newAn.classList.add('HotspotAnnotation', 'HiddenAnnotation') newAn.innerText = hsLabel newHs.appendChild(newAn) newLabel = document.createElement('span') newLabel.innerText = hsTag || (hsSlot) newHs.appendChild(newLabel) hotspotsObj.push(newHs) } try { [_, mvconfig] = extractMvconfig() } catch (err) { console.warn('Failed to read model config:' + err.message) return false } const currentSet = $('model-viewer').attr('currentSet') || 'default' if (currentSet != 'default' && mvconfig.annotationSets[currentSet]) { mvconfig.annotationSets[currentSet].forEach(hs => { createHotspot(hs, slotNum) slotNum += 1 }) } Object.keys(mvconfig.annotations).forEach(hs => { if (currentSet != 'default' && mvconfig.annotationSets[currentSet] && mvconfig.annotationSets[currentSet].includes(hs)) { return } let label = (currentSet != 'default' && mvconfig.annotationSets[currentSet]) ? '-' : null createHotspot(hs, slotNum, label) slotNum += 1 }) $('model-viewer button').remove() const mView = $('model-viewer')[0] hotspotsObj.forEach(hs => { mView.appendChild(hs) }) return { set: Object.keys(mvconfig.annotationSets), view: Object.keys(mvconfig.viewerConfig) } } /** * Parses the current hotspots into json object * and writes the json string to the edit panel * * @return {bool} true on successful write to edit panel */ writeMvconfig = function () { let annotationsObj = {} currentButtons = $('.Hotspot').each(function() { let buttonEl = $(this)[0] annotationsObj[buttonEl.childNodes[0].innerText] = { "data-position": buttonEl.getAttribute('data-position'), "data-normal": buttonEl.getAttribute('data-normal'), "data-orbit": buttonEl.getAttribute('data-orbit'), "data-target": buttonEl.getAttribute('data-target') } }) if (Object.keys(annotationsObj).length === 0) return false const [currentText, mvconfig] = extractMvconfig() mvconfig.annotations = annotationsObj const newText = currentText.replace(/(.*?)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`) $('#wpTextbox1').val(newText) return true } /** * Convert all radian values returned by model-viewer to degrees * * @param {string} orbString string with any number of `(number)rad` sub strings * @param {number|array} fix Optional: number of decimal places to return in converted numbers * @return {string} string with all radian values and units converted to degrees */ orb2degree = function(orbString, fix = null) { let degArray = orbString.split(' ').map(s => { if (s.includes('rad')) { return (Number.parseFloat(s) / Math.PI * 180) + 'deg' } else { return s } }) if (fix && !['number', 'object'].includes(typeof fix)) { console.warn('orb2degree: fix parameter invalid type. Ignoring.') fix = null } if (fix) { degArray = degArray.map((v, idx) => { let fixReg = new RegExp('(\\d*.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)') return v.replace(fixReg,'$1$3') }) } return degArray.join(' ') } /** * Set camera control setting for the current view * * @param {string} view * @return {bool} new camera-controls setting */ toggleCameraControl = function(view) { let [currentText, mvconfig] = extractMvconfig() const currentView = (mvconfig.viewerConfig[view]) ? view : 'default' const newControl = !mvconfig.viewerConfig[currentView]['camera-controls'] if (newControl) { mvconfig.viewerConfig[currentView]['camera-controls'] = newControl } else { delete mvconfig.viewerConfig[currentView]['camera-controls'] } const textUpdate = currentText.replace(/(?<=)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) $('#wpTextbox1').val(textUpdate) return newControl } /** * Add a new annoation set object to annotatsionSets array * * @param {string} newSet name of new view config */ addAnnotationSet = function(newSet) { const mView = $('model-viewer')[0] let [currentText, mvconfig] = extractMvconfig() mvconfig.annotationSets[newSet] = [] const textUpdate = currentText.replace(/(?<=)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) $('#wpTextbox1').val(textUpdate) selectAnnotationSet(newSet) } /** * Add a new set of view configurations to viewerConfig * * @param {string} newView name of new view config */ addViewConfig = function(newView) { const mView = $('model-viewer')[0] let [currentText, mvconfig] = extractMvconfig() mvconfig.viewerConfig[newView] = { "camera-controls": true } const textUpdate = currentText.replace(/(?<=)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) $('#wpTextbox1').val(textUpdate) selectViewConfig(newView) } /** * Switch the current model-viewer attributes to a different * set of configurations in the mvconfig data * * @param {string} view the view name (viewerConfig object key) */ selectViewConfig = function(view) { const mView = $('model-viewer')[0] let [_, mvconfig] = extractMvconfig() const selectView = (mvconfig.viewerConfig[view]) ? view : 'default' const viewConfig = mvconfig.viewerConfig[selectView] const settings = [ "camera-controls", "disable-pan", "disable-tap", "touch-action", "disable-zoom", "orbit-sensitivity", "zoom-sensitivity", "pan-sensitivity", "auto-rotate", "auto-rotate-delay", "rotation-per-second", "interaction-prompt-style", "interaction-prompt-threshold", "camera-orbit", "camera-target", "field-of-view", "max-camera-orbit", "min-camera-orbit", "max-field-of-view", "min-field-of-view", "poster", "ar", "ar-modes", "ar-scale", "ar-placement" ] settings.forEach(s => { if (viewConfig[s]) { mView.setAttribute(s,viewConfig[s]) } else { mView.removeAttribute(s) } }) } /** * Set new default camera orbit and send values to the preview * editor */ writeCameraOrbit = function() { const mView = $('model-viewer')[0] const newOrbit = orb2degree(mView.getCameraOrbit().toString(),[2,2,5]) mView.setAttribute('camera-orbit', newOrbit) const targetObj = mView.getCameraTarget() const newTarget = `${targetObj.x.toFixed(5)}m ${targetObj.y.toFixed(5)}m ${targetObj.z.toFixed(5)}m` mView.setAttribute('camera-target', newTarget) const newField = mView.getFieldOfView().toFixed(5) + 'deg' mView.setAttribute('field-of-view',newField) let [currentText, mvconfig] = extractMvconfig() mvconfig.viewerConfig.default['camera-orbit'] = newOrbit mvconfig.viewerConfig.default['camera-target'] = newTarget mvconfig.viewerConfig.default['field-of-view'] = newField const textUpdate = currentText.replace(/(?<=)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) $('#wpTextbox1').val(textUpdate) } /** * Set new camera orbit limits and send values to the preview * editor * * @param {string} axis [yaw|pitch] orbit value to set * @param {string} limit [max|min] limit value to set */ writeCameraLimit = function(axis, limit) { const mView = $('model-viewer')[0] const newOrbit = orb2degree(mView.getCameraOrbit().toString(),[2,2,5]) const newOrbitVals = newOrbit.split(' ') const valueIndex = (axis == 'yaw') ? 0 : 1 let [currentText, mvconfig] = extractMvconfig() const oldOrbit = mvconfig.viewerConfig.default[`${limit}-camera-orbit`] let oldOrbitVals = (oldOrbit) ? oldOrbit.split(' ') : Array(3).fill('auto') oldOrbitVals[valueIndex] = newOrbitVals[valueIndex] mvconfig.viewerConfig.default[`${limit}-camera-orbit`] = oldOrbitVals.join(' ') mView.setAttribute(`${limit}-camera-orbit`, oldOrbitVals.join(' ')) const textUpdate = currentText.replace(/(?<=)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`) $('#wpTextbox1').val(textUpdate) }