import math from './math.js' import * as THREE from 'three' var common = { valueFromHash(e, t) { var i = new RegExp('[#&?]' + e + '=([^#&?]*)'), n = i.exec(window.location.href) if (!n) return t var r = n[1] return 'boolean' == typeof t ? 'true' === r || '1' === r : 'number' == typeof t ? parseFloat(r) : window.decodeURIComponent(r) }, lowerMedian(e, t) { if (0 === e.length) return null ;(t = t || 2), e.sort(function (e, t) { return e - t }) var i = Math.floor(e.length / t) return e[i] }, stableSort(e, t) { return e .map(function (e, t) { return { value: e, index: t, } }) .sort(function (e, i) { var n = t(e.value, i.value) return 0 !== n ? n : e.index - i.index }) .map(function (e) { return e.value }) }, sortByScore: function (list, request, rank) { var i = request ? common.filterAll(list, request) : list return 0 === i.length ? [] : (i = i .map(function (e) { let scores = rank.map(function (f) { return f(e) }) //add return { item: e, scores, score: scores.reduce(function (t, i) { return t + i }, 0), } }) .sort(function (e, t) { return t.score - e.score })) }, filterAll(e, t) { return e.filter(function (e) { return t.every(function (t) { return t(e) }) }) }, getMixedSet: function (arr1, arr2) { //交集 return arr1.filter(item => arr2.includes(item)) }, getUnionSet: function (arr1, arr2) { //并集 return arr1.concat(arr2.filter(item => !arr1.includes(item))) }, getDifferenceSet: function (arr1, arr2) { //差集 不能识别重复的,如getDifferenceSet([1,2,2],[1,1,2]) 为空 var arr11 = arr1.filter(item => !arr2.includes(item)) var arr22 = arr2.filter(item => !arr1.includes(item)) return arr11.concat(arr22) }, getDifferenceSetMuti: function (arr) { //收集绝对没有重复的元素,也就是判断出现次数=1的 var set = [] arr.forEach(arr1 => { arr1.forEach(item => { var index = set.indexOf(item) if (index > -1) { set.splice(index, 1) } else { set.push(item) } }) }) return set }, pushToGroupAuto: function (items, groups, recognizeFunction, judgeRelationFun) { //自动分组。 items是将分到一起的组合。items.length = 1 or 2. let isSame = (a, b) => { return a == b || (recognizeFunction && recognizeFunction(a, b)) } var atGroups = groups.filter(group => group.find( item => (isSame(item, items[0]) || isSame(item, items[1])) && (!judgeRelationFun || judgeRelationFun(group)) //根据关系进一步判断是否应该一组 ) ) if (atGroups.length) { //在不同组 //因为items是一组的,所以先都放入组1 items.forEach(item => { if (!atGroups[0].includes(item)) atGroups[0].push(item) }) if (atGroups.length > 1) { //如果在不同组,说明这两个组需要合并 var combineGroup = [] combineGroup.relationships = [items.slice()] atGroups.forEach(group => { let relationships = common.getUnionSet(combineGroup.relationships, group.relationships) combineGroup = common.getUnionSet(combineGroup, group) combineGroup.relationships = relationships groups.splice(groups.indexOf(group), 1) }) groups.push(combineGroup) } else { atGroups[0].relationships.push(items.slice()) } } else { //直接加入为一组 items.relationships = [items.slice()] groups.push(items) } }, disconnectGroup: function (pairs, groups, recognizeFunction) { //将atGroup中的pairs关系解除,然后重新分组 let isSame = (a, b) => { return a == b || (recognizeFunction && recognizeFunction(a, b)) } let oldGroups = groups.slice() pairs.forEach(items => { let relationship let atGroup = groups.find(group => { let r = group.relationships.find(arr => items.every(e => arr.some(a => isSame(a, e)))) if (r) { relationship = r return true } }) //能找到relationships 有包含items的, 代表它们有绑定关系 if (!atGroup) return //断开连接时,因为组内没有其他成员的连接信息,所以需要清除整组,并将剩余的一个个重新连接 groups.splice(groups.indexOf(atGroup), 1) //删除 atGroup.relationships.splice(atGroup.relationships.indexOf(relationship), 1) let newGroups_ = [] //为了防止裂解的该组(因之前有judgeRelationFun但现在没传)混入其他组,先放一个空组合里 atGroup.relationships.forEach(pair => { //然后再重新生成这两个和组的关系,各自分组 common.pushToGroupAuto(pair, newGroups_, recognizeFunction) }) groups.push(...newGroups_) }) let newGroups = groups.filter(e => !oldGroups.includes(e)) return { newGroups } }, removeFromGroup: function (items, atGroup, groups, recognizeFunction) { //将该组移除items中的所有元素,以及包含它的关系 let isSame = (a, b) => { return a == b || (recognizeFunction && recognizeFunction(a, b)) } let newRelations = atGroup.relationships.filter(arr => !arr.some(e => items.some(item => isSame(e, item)))) if (newRelations.length == atGroup.relationships) return //断开连接时,因为组内没有其他成员的连接信息,所以需要清除整组,并将剩余的一个个重新连接 groups.splice(groups.indexOf(atGroup), 1) //删除 let newGroups = [] //为了防止裂解的该组(因之前有judgeRelationFun但现在没传)混入其他组,先放一个空组合里 atGroup.relationships.forEach(pair => { //然后再重新生成这两个和组的关系,各自分组 common.pushToGroupAuto(pair, newGroups, recognizeFunction) }) groups.push(...newGroups) return { newGroups } }, } common.dataURLtoBlob = function (dataurl) { //将base64转换blob var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new Blob([u8arr], { type: mime }) } common.dataURLtoFile = function (dataurl, filename) { //将base64转换为文件 var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new File([u8arr], filename, { type: mime }) } common.saveFile = function (data, filename, cb) { var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a') save_link.href = data save_link.download = filename var event = document.createEvent('MouseEvents') event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null) save_link.dispatchEvent(event) cb && cb() } common.replaceAll = function (str, f, e) { //f全部替换成e var reg = new RegExp(f, 'g') //创建正则RegExp对象 return str.replace(reg, e) } common.randomWord = function (randomFlag, min, max) { //随机字符串 var str = '', range = min, arr = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ] if (randomFlag) { // 随机长度 range = Math.round(Math.random() * (max - min)) + min } for (var i = 0; i < range; i++) { var pos = Math.round(Math.random() * (arr.length - 1)) str += arr[pos] } return str } common.getRandomSid = function () { //5-7位随机字符串 + 6位时间 为热点准备 var pre = common.randomWord(true, 5, 7) var post = new Date().getTime() + '' var len = post.length post = post.substring(len - 8, len - 5) + post.substring(len - 3, len) //其实还是有可能重复的.... return pre + post } common.getTime = function (second) { //秒 var str = '' //不支持大于60分钟的时间哟 var minute = parseInt(second / 60) if (minute < 10) str += '0' str += minute second = parseInt(second % 60) + '' if (second.length == 1) second = '0' + second str = str + ':' + second return str } ;(common.CloneJson = function (data) { var str = JSON.stringify(data) return JSON.parse(str) }), (common.CloneObject = function (copyObj, result, isSimpleCopy, simpleCopyList = []) { //isSimpleCopy 只复制最外层 //复制json result的可能:普通数字或字符串、普通数组、复杂对象 simpleCopyList.push(THREE.Object3D) //遇到simpleCopyList中的类直接使用不拷贝 if (!copyObj || typeof copyObj == 'number' || typeof copyObj == 'string' || copyObj instanceof Function || simpleCopyList.some(className => copyObj instanceof className)) { return copyObj } result = result || {} if (copyObj instanceof Array) { return copyObj.map(e => { return this.CloneObject(e) }) } else { if (copyObj.clone instanceof Function) { //解决一部分 return copyObj.clone() } } for (var key in copyObj) { if (copyObj[key] instanceof Object && !isSimpleCopy) result[key] = this.CloneObject(copyObj[key]) else result[key] = copyObj[key] //如果是函数类同基本数据,即复制引用 } return result }), (common.CloneClassObject = function (copyObj) { //复杂类对象 var newobj = new copyObj.constructor() this.CopyClassObject(newobj, copyObj) return newobj }), (common.CopyClassObject = function (targetObj, copyObj) { //复杂类对象 for (let i in copyObj) { if (i in copyObj.__proto__) break //到函数了跳出 targetObj[i] = this.CloneObject(copyObj[i], null) } }), (common.ifSame = function (object1, object2) { if (object1 == object2) return true // 0 != undefined , 0 == '' else if (!object1 || !object2) return false else if (object1.constructor != object2.constructor) { return false } else if (object1 instanceof Array) { if (object1.length != object2.length) return false var _object2 = object2.slice(0) for (let i = 0; i < object1.length; i++) { var u = _object2.find(e => ifSame(object1[i], e)) if (u == void 0 && !_object2.includes(u) && !object1.includes(u)) return false else { let index = _object2.indexOf(u) _object2.splice(index, 1) } } return true } else if (object1.equals instanceof Function) { //复杂数据仅支持这种,其他的可能卡住? return object1.equals(object2) } else if (typeof object1 == 'number' || typeof object1 == 'string') { if (isNaN(object1) && isNaN(object2)) return true else return object1 == object2 } else if (typeof object1 == 'object') { var keys1 = Object.keys(object1) var keys2 = Object.keys(object2) if (!ifSame(keys1, keys2)) return false for (let i in object1) { var same = ifSame(object1[i], object2[i]) if (!same) return false } return true } else { console.log('isSame出现例外') } }) common.intervalTool = { //延时update,防止卡顿 list: [], isWaiting: function (name, func, delayTime) { let item = this.list.find(e => e.name == name) if (!item) { //如果没有该项, 则加入循环 let ifContinue = func() item = { name } this.list.push(item) setTimeout(() => { var a = this.list.indexOf(item) this.list.splice(a, 1) if (item.requestUpdate || ifContinue) this.isWaiting(name, func, delayTime) //循环 }, delayTime) } else { //如果有该项,说明现在请求下一次继续更新 /* if(delayTime == 0){//想立刻更新一次 func() }else{ */ item.requestUpdate = true //} } }, } common.batchHandling = { //分批处理 lists: [], getSlice: function (name, items, { stopWhenAllUsed, minCount = 5, maxCount = 100, durBound1, durBound2, maxUseCount }) { if ( items.length == 0 || ((maxUseCount = maxUseCount == void 0 ? common.getBestCount({ name, minCount, maxCount, durBound1, durBound2, ifLog: false }) : maxUseCount), !maxUseCount) //本次最多可以使用的个数 ) { return { list: [] } } if (!this.lists[name]) this.lists[name] = { list: [] } //更新列表项目,但不变原来的顺序 let list = this.lists[name].list.filter(a => items.some(item => a.item == item)) //去掉已经不在items里的项目 this.lists[name].list = list items.forEach(item => { //增加新的项目。 if (!list.some(a => a.item == item)) { list.push({ item, count: 0 }) } }) //至此,在后排的都是未使用的 let unUsed = list.filter(e => e.count == 0) //未使用的项目(count为0)优先 let result = [] unUsed.slice(0, maxUseCount).forEach(e => { result.push(e.item) e.count++ }) if (unUsed.length > maxUseCount) { //还是剩有未使用的项目,等待下一次 } else { //所有项目都能使用一次 if (!stopWhenAllUsed) { //若不是全部使用就停止 let wholeCount = Math.min(items.length, maxUseCount) let restCount = wholeCount - result.length //补齐 list.slice(0, restCount).forEach(e => { result.push(e.item) e.count++ }) } list.forEach(e => e.count--) //复原,等待新的循环 } return { list: result } }, addSliceListen({ getList, callback, minCount, maxCount, durBound1, durBound2, maxHistory, player }) { let unUpdate, lastUpdate player.on('update', e => { if (player.flying) return let waitForUpdate = getList() let stopWhenAllUsed = !player.lastFrameChanged let standardUpdate = player.lastFrameChanged || !lastUpdate //相机变化或第一次 let list if (standardUpdate) { list = waitForUpdate unUpdate = null } else { if (!unUpdate) { unUpdate = common.getDifferenceSet(waitForUpdate, lastUpdate) //unUpdate = unUpdate.filter(e => e.visible) //如飞出后最后一次更新之后,都隐藏了,隐藏的就不用更新了 } list = unUpdate } let result = common.batchHandling.getSlice('ifVideoInsight', list, { stopWhenAllUsed, minCount, maxCount, durBound1: 3, durBound2: 13, maxHistory: 3 }) //iphonex稳定后大概在7-10。 let updateList = result.list //updateList.length && console.log(updateList.map(e=>e.sid)) updateList.forEach(callback) if (!standardUpdate) { //相机停止变化后只更新还未更新的 unUpdate = common.getDifferenceSet(unUpdate, updateList) } lastUpdate = updateList }) }, } common.getBestCount = (function () { let lastCount = {} return function ({ name, minCount = 1, maxCount = 6, durBound1 = 1, durBound2 = 4, ifLog, maxHistory }) { let timeStamp = performance.getEntriesByName('loop-start') let count if (timeStamp.length) { let dur = performance.now() - timeStamp[timeStamp.length - 1].startTime /*let k = -(maxCount - minCount) / (durBound2 - durBound1) let m = maxCount - durBound1 * k count = THREE.MathUtils.clamp(Math.round(k * dur + m), minCount, maxCount) //dur在iphoneX中静止有7,pc是2 */ count = Math.round(math.linearClamp(dur, durBound1, durBound2, maxCount, minCount)) if (maxHistory) { if (!lastCount[name]) lastCount[name] = [] if (count == 0 && lastCount[name].length > maxHistory - 1 && !lastCount[name].some(e => e > 0)) { count = 1 } lastCount[name].push(count) if (lastCount[name].length > maxHistory) lastCount[name].splice(0, 1) } ifLog && console.log(name, count, ' ,dur:', dur.toFixed(3)) } else { count = maxCount // ? } //主要在手机端有效果。 return count } })() common.timeMeasuring = { reportTimings: false, collection: {}, registerCollect(name, o) { this.collection[name] = o o.measures = [] o.sum = 0 }, addTimeMark: function (name, type, ifLog) { let record = this.collection[name] let now = performance.now() let needRecord = record && (record.measures.length < record.minCount || now - record.lastAddTime > record.refreshTime) //间隔时间超过refreshTime重新收集 if (needRecord || this.reportTimings) { if (type == 'end' && performance.getEntriesByName(name + '-start').length == 0) return performance.mark(name + '-' + type) if (type == 'end') { let measure = performance.measure(name, name + '-start', name + '-end') if (!measure) { //console.error('没找到measure',name) //可能是其他地方报错了没进行下去所以找不到 return } if (ifLog) console.log(name, '耗时', measure.duration.toFixed(3)) if (needRecord) { if (record.measures.length >= record.minCount) { //先清空上一轮的 record.measures = [] record.sum = 0 } record.measures.push(measure.duration) record.sum += measure.duration record.mean = record.sum / record.measures.length record.measures.sort((a, b) => a - b) record.median = record.measures[parseInt(record.measures.length / 2)] record.lastAddTime = now if (record.measures.length == record.minCount) { //console.log(record) } } } } }, report: function (timestamp) { //原resolveTimings //打印用时。 注:performance手机的精度只到整数位 if (!this.toggle) { this.toggle = timestamp } let duration = timestamp - this.toggle if (duration > 1000.0) { if (this.reportTimings) { let measures = performance.getEntriesByType('measure') let names = new Set() for (let measure of measures) { names.add(measure.name) } let groups = new Map() for (let name of names) { groups.set(name, { measures: [], sum: 0, n: 0, min: Infinity, max: -Infinity, }) } for (let measure of measures) { let group = groups.get(measure.name) group.measures.push(measure) group.sum += measure.duration group.n++ group.min = Math.min(group.min, measure.duration) group.max = Math.max(group.max, measure.duration) } for (let [name, group] of groups) { group.mean = group.sum / group.n group.measures.sort((a, b) => a.duration - b.duration) if (group.n === 1) { group.median = group.measures[0].duration } else if (group.n > 1) { group.median = group.measures[parseInt(group.n / 2)].duration } } let cn = Array.from(names).reduce((a, i) => Math.max(a, i.length), 0) + 5 let cmin = 5 let cmed = 5 let cmax = 5 let csam = 4 let message = ` ${'NAME'.padEnd(cn)} |` + ` ${'MIN'.padStart(cmin)} |` + ` ${'MEDIAN'.padStart(cmed)} |` + ` ${'MAX'.padStart(cmax)} |` + ` ${'AVE'.padStart(cmax)} |` + ` ${'SAMPLES'.padStart(csam)} \n` message += ` ${'-'.repeat(message.length)}\n` names = Array.from(names).sort() for (let name of names) { let group = groups.get(name) let min = group.min.toFixed(2) let median = group.median.toFixed(2) let max = group.max.toFixed(2) let n = group.n let ave = group.mean.toFixed(2) //add message += ` ${name.padEnd(cn)} |` + ` ${min.padStart(cmin)} |` + ` ${median.padStart(cmed)} |` + ` ${max.padStart(cmax)} |` + ` ${ave.padStart(cmax)} |` + ` ${n.toString().padStart(csam)}\n` } message += `\n` console.log(message) } performance.clearMarks() performance.clearMeasures() this.toggle = timestamp } }, } export default common