text: /**
   * @file Utils
   * @author Alexander Rose <alexander.rose@weirdbyte.de>
   * @private
   */
  
  import { Vector2, Vector3, Matrix4, Quaternion } from '../lib/three.es6.js'
  
  import { DecompressorRegistry } from './globals.js'
  
  function getQuery (id) {
    if (typeof window === 'undefined') return undefined
  
    var a = new RegExp(`${id}=([^&#=]*)`)
    var m = a.exec(window.location.search)
  
    if (m) {
      return decodeURIComponent(m[1])
    } else {
      return undefined
    }
  }
  
  function boolean (value) {
    if (!value) {
      return false
    }
  
    if (typeof value === 'string') {
      return /^1|true|t|yes|y$/i.test(value)
    }
  
    return true
  }
  
  function defaults (value, defaultValue) {
    return value !== undefined ? value : defaultValue
  }
  
  function pick (object) {
    var properties = [].slice.call(arguments, 1)
    return properties.reduce((a, e) => {
      a[ e ] = object[ e ]
      return a
    }, {})
  }
  
  function flatten (array, ret) {
    ret = defaults(ret, [])
    for (let i = 0; i < array.length; i++) {
      if (Array.isArray(array[i])) {
        flatten(array[i], ret)
      } else {
        ret.push(array[i])
      }
    }
    return ret
  }
  
  function getProtocol () {
    var protocol = window.location.protocol
    return protocol.match(/http(s)?:/gi) === null ? 'http:' : protocol
  }
  
  function getBrowser () {
    if (typeof window === 'undefined') return false
  
    var ua = window.navigator.userAgent
  
    if (/Opera|OPR/.test(ua)) {
      return 'Opera'
    } else if (/Chrome/i.test(ua)) {
      return 'Chrome'
    } else if (/Firefox/i.test(ua)) {
      return 'Firefox'
    } else if (/Mobile(\/.*)? Safari/i.test(ua)) {
      return 'Mobile Safari'
    } else if (/MSIE/i.test(ua)) {
      return 'Internet Explorer'
    } else if (/Safari/i.test(ua)) {
      return 'Safari'
    }
  
    return false
  }
  
  function getAbsolutePath (relativePath) {
    var loc = window.location
    var pn = loc.pathname
    var basePath = pn.substring(0, pn.lastIndexOf('/') + 1)
  
    return loc.origin + basePath + relativePath
  }
  
  function deepCopy (src) {
    if (typeof src !== 'object') {
      return src
    }
  
    var dst = Array.isArray(src) ? [] : {}
  
    for (var key in src) {
      dst[ key ] = deepCopy(src[ key ])
    }
  
    return dst
  }
  
  function download (data, downloadName) {
    // using ideas from https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js
  
    if (!data) return
  
    downloadName = downloadName || 'download'
  
    var isSafari = getBrowser() === 'Safari'
    var isChromeIos = /CriOS\/[\d]+/.test(window.navigator.userAgent)
  
    var a = document.createElement('a')
  
    function openUrl (url) {
      var opened = window.open(url, '_blank')
      if (!opened) {
        window.location.href = url
      }
    }
  
    function open (str) {
      openUrl(isChromeIos ? str : str.replace(/^data:[^;]*;/, 'data:attachment/file;'))
    }
  
    if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
      // native saveAs in IE 10+
      navigator.msSaveOrOpenBlob(data, downloadName)
    } else if ((isSafari || isChromeIos) && window.FileReader) {
      if (data instanceof window.Blob) {
        // no downloading of blob urls in Safari
        var reader = new window.FileReader()
        reader.onloadend = function () {
          open(reader.result)
        }
        reader.readAsDataURL(data)
      } else {
        open(data)
      }
    } else {
      if (data instanceof window.Blob) {
        data = window.URL.createObjectURL(data)
      }
  
      if ('download' in a) {
        // download link available
        a.style.display = 'hidden'
        document.body.appendChild(a)
        a.href = data
        a.download = downloadName
        a.target = '_blank'
        a.click()
        document.body.removeChild(a)
      } else {
        openUrl(data)
      }
  
      if (data instanceof window.Blob) {
        window.URL.revokeObjectURL(data)
      }
    }
  }
  
  function submit (url, data, callback, onerror) {
    if (data instanceof window.FormData) {
      var xhr = new window.XMLHttpRequest()
      xhr.open('POST', url)
  
      xhr.addEventListener('load', function () {
        if (xhr.status === 200 || xhr.status === 304) {
          callback(xhr.response)
        } else {
          if (typeof onerror === 'function') {
            onerror(xhr.status)
          }
        }
      }, false)
  
      xhr.send(data)
    } else {
      throw new Error('submit: data must be a FormData instance.')
    }
  }
  
  function open (callback, extensionList) {
    extensionList = extensionList || [ '*' ]
  
    var fileInput = document.createElement('input')
    fileInput.type = 'file'
    fileInput.multiple = true
    fileInput.style.display = 'hidden'
    document.body.appendChild(fileInput)
    fileInput.accept = '.' + extensionList.join(',.')
    fileInput.addEventListener('change', function (e) {
      callback(e.target.files)
    }, false)
  
    fileInput.click()
  }
  
  function getFileInfo (file) {
    var compressedExtList = DecompressorRegistry.names
  
    var path, compressed, protocol
  
    if ((typeof window !== 'undefined' && file instanceof window.File) ||
        (typeof window !== 'undefined' && file instanceof window.Blob)
    ) {
      path = file.name || ''
    } else {
      path = file
    }
    var queryIndex = path.lastIndexOf('?')
    var query = queryIndex !== -1 ? path.substring(queryIndex) : ''
    path = path.substring(0, queryIndex === -1 ? path.length : queryIndex)
  
    var name = path.replace(/^.*[\\/]/, '')
    var base = name.substring(0, name.lastIndexOf('.'))
  
    var nameSplit = name.split('.')
    var ext = nameSplit.length > 1 ? nameSplit.pop().toLowerCase() : ''
  
    var protocolMatch = path.match(/^(.+):\/\/(.+)$/)
    if (protocolMatch) {
      protocol = protocolMatch[ 1 ].toLowerCase()
      path = protocolMatch[ 2 ]
    }
  
    var dir = path.substring(0, path.lastIndexOf('/') + 1)
  
    if (compressedExtList.includes(ext)) {
      compressed = ext
      var n = path.length - ext.length - 1
      ext = path.substr(0, n).split('.').pop().toLowerCase()
      var m = base.length - ext.length - 1
      base = base.substr(0, m)
    } else {
      compressed = false
    }
  
    return {
      'path': path,
      'name': name,
      'ext': ext,
      'base': base,
      'dir': dir,
      'compressed': compressed,
      'protocol': protocol,
      'src': file,
      'query': query
    }
  }
  
  function throttle (func, wait, options) {
    // from http://underscorejs.org/docs/underscore.html
  
    var context, args, result
    var timeout = null
    var previous = 0
  
    if (!options) options = {}
  
    var later = function () {
      previous = options.leading === false ? 0 : Date.now()
      timeout = null
      result = func.apply(context, args)
      if (!timeout) context = args = null
    }
  
    return function throttle () {
      var now = Date.now()
      if (!previous && options.leading === false) previous = now
      var remaining = wait - (now - previous)
      context = this
      args = arguments
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout)
          timeout = null
        }
        previous = now
        result = func.apply(context, args)
        if (!timeout) context = args = null
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining)
      }
  
      return result
    }
  }
  
  function lexicographicCompare (elm1, elm2) {
    if (elm1 < elm2) return -1
    if (elm1 > elm2) return 1
    return 0
  }
  
  /**
   * Does a binary search to get the index of an element in the input array
   * @function
   * @example
   * var array = [ 1, 2, 3, 4, 5, 6 ];
   * var element = 4;
   * binarySearchIndexOf( array, element );  // returns 3
   *
   * @param {Array} array - sorted array
   * @param {Anything} element - element to search for in the array
   * @param {Function} [compareFunction] - compare function
   * @return {Number} the index of the element or -1 if not in the array
   */
  function binarySearchIndexOf (array, element, compareFunction = lexicographicCompare) {
    let low = 0
    let high = array.length - 1
    while (low <= high) {
      const mid = (low + high) >> 1
      const cmp = compareFunction(element, array[ mid ])
      if (cmp > 0) {
        low = mid + 1
      } else if (cmp < 0) {
        high = mid - 1
      } else {
        return mid
      }
    }
    return -low - 1
  }
  
  function binarySearchForLeftRange (array, leftRange) {
    let high = array.length - 1
    if (array[ high ] < leftRange) return -1
    let low = 0
    while (low <= high) {
      const mid = (low + high) >> 1
      if (array[ mid ] >= leftRange) {
        high = mid - 1
      } else {
        low = mid + 1
      }
    }
    return high + 1
  }
  
  function binarySearchForRightRange (array, rightRange) {
    if (array[ 0 ] > rightRange) return -1
    let low = 0
    let high = array.length - 1
    while (low <= high) {
      const mid = (low + high) >> 1
      if (array[ mid ] > rightRange) {
        high = mid - 1
      } else {
        low = mid + 1
      }
    }
    return low - 1
  }
  
  function rangeInSortedArray (array, min, max) {
    const indexLeft = binarySearchForLeftRange(array, min)
    const indexRight = binarySearchForRightRange(array, max)
    if (indexLeft === -1 || indexRight === -1 || indexLeft > indexRight) {
      return 0
    } else {
      return indexRight - indexLeft + 1
    }
  }
  
  function dataURItoImage (dataURI) {
    if (typeof importScripts !== 'function') {
      var img = document.createElement('img')
      img.src = dataURI
      return img
    }
  }
  
  function uniqueArray (array) {
    return array.sort().filter(function (value, index, sorted) {
      return (index === 0) || (value !== sorted[ index - 1 ])
    })
  }
  
  // String/arraybuffer conversion
  
  function uint8ToString (u8a) {
    var chunkSize = 0x7000
  
    if (u8a.length > chunkSize) {
      var c = []
  
      for (var i = 0; i < u8a.length; i += chunkSize) {
        c.push(String.fromCharCode.apply(
          null, u8a.subarray(i, i + chunkSize)
        ))
      }
  
      return c.join('')
    } else {
      return String.fromCharCode.apply(null, u8a)
    }
  }
  
  function uint8ToLines (u8a, chunkSize, newline) {
    chunkSize = chunkSize !== undefined ? chunkSize : 1024 * 1024 * 10
    newline = newline !== undefined ? newline : '\n'
  
    var partialLine = ''
    var lines = []
  
    for (var i = 0; i < u8a.length; i += chunkSize) {
      var str = uint8ToString(u8a.subarray(i, i + chunkSize))
      var idx = str.lastIndexOf(newline)
  
      if (idx === -1) {
        partialLine += str
      } else {
        var str2 = partialLine + str.substr(0, idx)
        lines = lines.concat(str2.split(newline))
  
        if (idx === str.length - newline.length) {
          partialLine = ''
        } else {
          partialLine = str.substr(idx + newline.length)
        }
      }
    }
  
    if (partialLine !== '') {
      lines.push(partialLine)
    }
  
    return lines
  }
  
  function getTypedArray (arrayType, arraySize) {
    switch (arrayType) {
      case 'int8':
        return new Int8Array(arraySize)
      case 'int16':
        return new Int16Array(arraySize)
      case 'int32':
        return new Int32Array(arraySize)
      case 'uint8':
        return new Uint8Array(arraySize)
      case 'uint16':
        return new Uint16Array(arraySize)
      case 'uint32':
        return new Uint32Array(arraySize)
      case 'float32':
        return new Float32Array(arraySize)
      default:
        throw new Error('arrayType unknown: ' + arrayType)
    }
  }
  
  function getUintArray (sizeOrArray, maxUnit) {
    const TypedArray = maxUnit > 65535 ? Uint32Array : Uint16Array
    return new TypedArray(sizeOrArray)
  }
  
  function ensureArray (value) {
    return Array.isArray(value) ? value : [value]
  }
  
  function ensureBuffer (a) {
    return (a.buffer && a.buffer instanceof ArrayBuffer) ? a.buffer : a
  }
  
  function _ensureClassFromArg (arg, constructor) {
    return arg instanceof constructor ? arg : new constructor(arg)
  }
  
  function _ensureClassFromArray (array, constructor) {
    if (array === undefined) {
      array = new constructor()
    } else if (Array.isArray(array)) {
      array = new constructor().fromArray(array)
    }
    return array
  }
  
  function ensureVector2 (v) {
    return _ensureClassFromArray(v, Vector2)
  }
  
  function ensureVector3 (v) {
    return _ensureClassFromArray(v, Vector3)
  }
  
  function ensureMatrix4 (m) {
    return _ensureClassFromArray(m, Matrix4)
  }
  
  function ensureQuaternion (q) {
    return _ensureClassFromArray(q, Quaternion)
  }
  
  function ensureFloat32Array (a) {
    return _ensureClassFromArg(a, Float32Array)
  }
  
  export {
    getQuery,
    boolean,
    defaults,
    pick,
    flatten,
    getProtocol,
    getBrowser,
    getAbsolutePath,
    deepCopy,
    download,
    submit,
    open,
    getFileInfo,
    throttle,
    binarySearchIndexOf,
    rangeInSortedArray,
    dataURItoImage,
    uniqueArray,
    uint8ToString,
    uint8ToLines,
    getTypedArray,
    getUintArray,
    ensureArray,
    ensureBuffer,
    ensureVector2,
    ensureVector3,
    ensureMatrix4,
    ensureQuaternion,
    ensureFloat32Array
  }
  