import * as THREE from "../../../libs/three.js/build/three.module.js"; import math from './math.js' import '../../../libs/other/UPNG.js' var Common = { autoPlayList:[], sortByScore: function(list, request, rank){ var i = request ? Common.filterAll(list, request) : list return 0 === i.length ? [] : i = i.map(function(e) { let results = rank.map(function(f){return f(e)}) let scores = results.map(e=>e.score != void 0 ? e.score : e) let logs = results.map(e=>e.log) return { item: e, scores, logs, score: scores.reduce(function(t, i) {//总分 return t + i }, 0) } }).sort(function(e, t) { return t.score - e.score; }) } , filterAll: function(e, t) { return e.filter(function (e) { return t.every(function (t) { return t(e) }) }) }, //--------------- find : function(list, request, rank, sortByScore ) { if(sortByScore){ var r = this.sortByScore(list, request, rank) return r[0] && r[0].item }else{ var i = request ? Common.filterAll(list, request) : list return 0 === i.length ? null : (rank && rank.forEach(function(e) { i = Common.stableSort(i, e) }), i[0]) } } , stableSort: function(e, f) {//用到排序函数,涉及到两个item相减 return e.map(function(e, i) { return { value: e, index: i } }).sort(function(e, u) { var n = f(e.value, u.value); return 0 !== n ? n : e.index - u.index //似乎就是加多了这一步:若差距为0,按照原顺序 }).map(function(e) { return e.value }) }, average: function (e, t) { if (0 === e.length) return null; for (var i = 0, n = 0, r = 0; r < e.length; r++) { var o = t ? e[r][t] : e[r]; i += o, n++ } return i / n }, //--------------------------- 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; } , CloneJson : function(data){ var str = JSON.stringify(data) return JSON.parse(str) } , CloneObject : function (copyObj, isSimpleCopy, simpleCopyList = [], judgeSimpleCopyFun) { //isSimpleCopy 只复制最外层 //复制json result的可能:普通数字或字符串、普通数组、复杂对象 judgeSimpleCopyFun || (judgeSimpleCopyFun=()=>{}) if (!copyObj || typeof copyObj == 'number' || typeof copyObj == 'string' ||copyObj.isObject3D || copyObj instanceof Function || simpleCopyList.some(className => copyObj instanceof className) || judgeSimpleCopyFun(copyObj)) { return copyObj } if (copyObj instanceof Array) { return copyObj.map(e => { return this.CloneObject(e, isSimpleCopy, simpleCopyList, judgeSimpleCopyFun) }) } else { if (copyObj.clone instanceof Function) { //解决一部分 return copyObj.clone() } } let result = {} for (var key in copyObj) { if (copyObj[key] instanceof Object && !isSimpleCopy ) result[key] = this.CloneObject(copyObj[key], isSimpleCopy, simpleCopyList, judgeSimpleCopyFun) else result[key] = copyObj[key] //如果是函数类同基本数据,即复制引用 } return result } , CloneClassObject :function(copyObj, {ignoreList=[],simpleCopyList=[]}={}){//复杂类对象 var newobj = new copyObj.constructor(); this.CopyClassObject(newobj, copyObj, {ignoreList,simpleCopyList}) return newobj } , CopyClassObject :function(targetObj, copyObj, {ignoreList=[],simpleCopyList=[]}={}){//复杂类对象 for(let i in copyObj){ if(i in copyObj.__proto__)break; //到函数了跳出 if(ignoreList.includes(i)){ continue; }else if(simpleCopyList.includes(i)){ targetObj[i] = copyObj[i] }else{ targetObj[i] = this.CloneObject(copyObj[i], false, simpleCopyList ) } /* else if(copyObj[i].clone instanceof Function ){ targetObj[i] = copyObj[i].clone() }else{ targetObj[i] = copyObj[i]; } */ } } , ifSame : function(object1, object2, simpleEqualClass=[]){ //对于复杂的类对象,若能简单判断就直接写进simpleEqualClass if(object1 == object2 )return true // 0 != undefined , 0 == '' else if(!object1 || !object2) return false else if(object1.constructor != object2.constructor){ return false }else if(simpleEqualClass.some(className => object1 instanceof className)){ return object1 == object2 }else if(object1 instanceof Array ) { if(object1.length != object2.length)return false; var _object2 = object2.slice(0); for(let i=0;iCommon.ifSame(object1[i], e, simpleEqualClass)); 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(!Common.ifSame(keys1,keys2,simpleEqualClass))return false; for(let i in object1){ var same = Common.ifSame(object1[i], object2[i],simpleEqualClass); if(!same)return false } return true }else{ console.log('isSame出现例外') } } , downloadFile : 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(); }, replaceAll : function (str, f, e ) { //f全部替换成e if(str.replaceAll ) return str.replaceAll(f, e) else{ let escapeRegExp = (string)=>{ return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } return str.replace(new RegExp(escapeRegExp(f), 'g'), e); /* var reg = new RegExp(f, "g"); //创建正则RegExp对象 这个没法转换'(' return str.replace(reg, e); //str.split(f).join(e); */ } }, dealURL(url=''){ let urlNew = this.replaceAll(url, "+", "%2B"); //this.replaceAll(url, "\\+", "%2B");// 浏览器似乎不支持访问带+的地址 urlNew = this.replaceAll(urlNew, "/.//", "/") //去除双斜杠(/.//) //urlNew = encodeURIComponent(url) return urlNew }, getNameFromURL(url, removePostfix){ if(!url)return '' let get = (e)=>{ let a = e.split('/').pop() if(removePostfix) a = a.split('.')[0] return a } if(url instanceof Array){ return url.map(e=>get(e)) } return get(url) }, //--------------------------- intervalTool:{ //延时update,防止卡顿 list:[], /* isWaiting:function(name, func, delayTime){ if(!this.list.includes(name)){ //如果没有该项, 则开始判断 var needWait = func(); //触发了改变,则等待一段时间后再自动判断 if(needWait){ this.list.push(name); setTimeout(()=>{ var a = this.list.indexOf(name); this.list.splice(a,1); this.isWaiting(name, func, delayTime) //循环 },delayTime) } } }, */ isWaiting:function(name, func, delayTime/* , autoCycle */){ let item = this.list.find(e=>e.name == name) if(!item){ //如果没有该项, 则加入循环 let ifContinue = func() item = {name, func, delayTime} /* if(name == 'processPriorityQueue'){ console.log('isWaiting', delayTime) } */ this.list.push(item); setTimeout(()=>{ var a = this.list.indexOf(item); this.list.splice(a,1); let {func, delayTime} = item if(item.requestUpdate || ifContinue ) this.isWaiting(name, func, delayTime) //循环 },delayTime) }else{//如果有该项,说明现在请求下一次继续更新 //if(delayTime == 0){//想立刻更新一次 // func() //}else{ //更新属性 item.func = func item.delayTime = delayTime item.requestUpdate = true //} } }, } , waitTool:{//定时器,在等待的这段时间内如果又触发则重新计时 list:[], wait(name, func, time){ let item = this.list.find(e=>e.name == name) let timer = setTimeout(()=>{ func() let index = this.list.indexOf(item) this.list.splice(index, 1) }, time) if(item){ clearTimeout(item.timer) }else{ item = {name} this.list.push(item) } item.timer = timer }, cancel(name){ let index = this.list.findIndex(e=>e.name == name) index>-1 && this.list.splice(index, 1) } } , pushToGroupAuto : function(items, groups, recognizeFunction, recognizeGroup){//自动分组。 items是将分到一起的组合。items.length = 1 or 2. recognizeFunction = recognizeFunction || function(){} if(recognizeGroup){ //有更复杂的识别处理,直接传递整个组 var atGroups = groups.filter(group=>recognizeGroup(group, items)) }else{ var atGroups = groups.filter(group=>group.find( item => items[0] == item || recognizeFunction(item, items[0]) || items[1] == item || items[1] && recognizeFunction(item, items[1]) )) } if(atGroups.length){//在不同组 //因为items是一组的,所以先都放入组1 items.forEach(item=> {if(!atGroups[0].includes(item)) atGroups[0].push(item);}) if(atGroups.length>1){//如果在不同组,说明这两个组需要合并 var combineGroup = [] atGroups.forEach(group=>{ combineGroup = Common.getUnionSet(combineGroup, group) groups.splice(groups.indexOf(group),1) }) groups.push(combineGroup) } }else{//直接加入为一组 groups.push(items) } }, getBestCount : (function(){ let lastCount = {} return function(name, minCount=1,maxCount=6, durBound1 = 1.2, durBound2 = 10, ifLog, maxHistory){ let timeStamp = performance.getEntriesByName("loop-start"); let count if(timeStamp.length){ let dur = performance.now() - timeStamp[timeStamp.length-1].startTime; //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) } if(ifLog){//注意,console.log本身用时挺高, 降4倍时可能占用0.5毫秒 name && count && console.log(name, count , ' ,dur:', dur.toFixed(3)) } }else{ count = maxCount // ? } //主要在手机端有效果。 return count } })(), getBestCountFPS(name, ifLog, minCount=1, maxCount=6, minFps = 10, maxFps = 60, minPanoCount = 200, maxPanoCount = 1000 ){ let fps = Potree.fps let count = math.linearClamp(fps, [minFps, maxFps], [minCount, maxCount]) let count2 = math.linearClamp(viewer.images360.panos.length, [minPanoCount, maxPanoCount], [minCount, maxCount]) count = (count + count2)/2 count = Math.round(count) if(ifLog){ name && count && console.log(name, count , ' ,fps:', fps.toFixed(3)) } return count }, batchHandling : {//分批处理 lists:[], getSlice : function(name, items , {stopWhenAllUsed, min=5,max=100, durBound1 , durBound2, useEquals , maxUseCount}){ if(items.length == 0 || ((maxUseCount = maxUseCount == void 0 ? Common.getBestCount(name, min,max , durBound1, durBound2 /* , true */ ) : maxUseCount), !maxUseCount) //本次最多可以使用的个数 ){ return {list:[]} } if(!this.lists[name]) this.lists[name] = {list:[] } //更新列表项目,但不变原来的顺序 let list = this.lists[name].list.filter(a=>items.some(item=> useEquals ? a.item.equals(item) : a.item == item))//去掉已经不在items里的项目 this.lists[name].list = list items.forEach(item=>{//增加新的项目。 if(!list.some(a=>useEquals ? a.item.equals(item) : 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--) //复原,等待新的循环 } /* result.forEach((e,i)=>{//有重复的 if( result.slice(0,i).some(a=>a.equals(e)) || result.slice(i+1).some(a=>a.equals(e)) ) { console.log(e) } }) */ return {list:result } } }, getRootWindow(){//获取包含Potree的根window let win = window try{ while(win.parent!=win && win.parent.Potree){ win = win.parent } if(window != win)return win }catch(e){ //console.log(e) //可能跨域,从而win.parent.Potree报错 console.log('getRootWindow 跨域') return } }, watch: function(object, propName, initialValue){ //监听某个属性的变化 let v = initialValue Object.defineProperty(object, propName, { get: function() { return v }, set: function(e) { console.warn('watch:',propName, e) v = e } }) }, imgAddLabel : function (img, labelImg, labelInfo = {}) { //图上加另一张小图,用于添加水印 let canvas if(img instanceof Image){ canvas = document.createElement('canvas') }else{ canvas = img } let context = canvas.getContext('2d') let marginLeft = labelInfo.bgMargin && labelInfo.bgMargin.left || 0 let marginRight = labelInfo.bgMargin && labelInfo.bgMargin.right || 0 let marginTop = labelInfo.bgMargin && labelInfo.bgMargin.top || 0 let marginBottom = labelInfo.bgMargin && labelInfo.bgMargin.bottom || 0 if(img instanceof Image){//如果img是canvas,说明已绘制在canvas上了就不用绘制了 let width = img.width + marginLeft + marginRight let height = img.height + marginTop + marginBottom canvas.width = width canvas.height = height if(labelInfo.bgColor){ context.fillStyle = 'rgba(' + labelInfo.bgColor.r + ',' + labelInfo.bgColor.g + ',' + labelInfo.bgColor.b + ',' + labelInfo.bgColor.a + ')'; context.fillRect(0,0,width,height); } context.drawImage(img, marginLeft, marginTop, img.width, img.height) } let labelWidth = labelInfo.widthRatioToImg ? img.width * labelInfo.widthRatioToImg : labelImg.width //widthRatioToImg:label的width占img的width的比例 let labelHeight = (labelWidth * labelImg.height) / labelImg.width if (labelInfo.leftRatioToImg == void 0 && labelInfo.rightRatioToImg != void 0) { labelInfo.leftRatioToImg = 1 - labelInfo.rightRatioToImg - (labelInfo.widthRatioToImg || labelImg.width / img.width) } if (labelInfo.topRatioToImg == void 0 && labelInfo.bottomRatioToImg != void 0) { labelInfo.topRatioToImg = 1 - labelInfo.bottomRatioToImg - labelHeight / img.height } let labelLeft = img.width * labelInfo.leftRatioToImg + marginLeft //leftRatioToImg:label的left占img的width的比例 let labelTop = img.height * labelInfo.topRatioToImg + marginTop //topRatioToImg:label的top占img的height的比例 context.globalAlpha = labelInfo.opacity != void 0 ? labelInfo.opacity : 1 context.drawImage(labelImg, labelLeft, labelTop, labelWidth, labelHeight) if(labelInfo.outputCanvas){ return canvas } var dataUrl = canvas.toDataURL('image/png', labelInfo.compressRatio) //Common.downloadFile(dataUrl, 'screenshot.png') context.clearRect(0,0,canvas.width,canvas.height) return dataUrl }, mobileAutoPlay(media, playFun){//移动端。不这么写video不会播放 . (2022.11.29: 可为何加了Hot.updateHots之后又会自动播了?https有关? if(this.autoPlayList.includes(media))return this.autoPlayList.push(media) viewer.addEventListener let events = ['global_touchstart','global_mousedown'] let fun = ()=>{ let index = this.autoPlayList.indexOf(media) if(index>-1){ console.log( 'try autoplay '+ media.src) playFun() this.autoPlayList.splice(index,1) events.forEach((eventName)=>{ viewer.removeEventListener(eventName,fun) }) } } events.forEach((eventName)=>{ viewer.addEventListener(eventName,fun) }) }, /* changeShaderToWebgl2(vs, fs, matType, otherReplaces=[]){//部分shader要根据webgl版本作更改 if(!Potree.settings.isWebgl2)return {vs, fs} let turnTo300 = matType != 'ShaderMaterial' && (vs.includes('gl_FragDepthEXT') || fs.includes('gl_FragDepthEXT') ) let addV300 = turnTo300 && matType != 'RawShaderMaterial' // RawShaderMaterial直接material.glslVersion = '300 es' 以加在define之前 let change = (shader, shaderType)=>{ let newShader = shader if(turnTo300){ //非shaderMaterial需要手动改为300 es的写法 addV300 && (newShader = '#version 300 es \n' + newShader) //需要加 #version 300 es。 three.js自带的渲染会自动加所以不用 newShader = newShader.replaceAll('varying ', shaderType == 'vs' ? 'out ' : 'in ') newShader = newShader.replaceAll('attribute ', 'in ') if(shaderType == 'fs'){ newShader = newShader.replaceAll('gl_FragColor', 'fragColor') newShader = newShader.replace('void main', 'out vec4 fragColor;\n void main' )//在void main前加入这个声明 } newShader = newShader.replaceAll('gl_FragDepthEXT','gl_FragDepth') newShader = newShader.replaceAll('texture2D','texture') newShader = newShader.replaceAll('textureCube','texture') } newShader = newShader.replace('#extension GL_EXT_frag_depth : enable','') newShader = newShader.replaceAll('defined(GL_EXT_frag_depth) &&','') otherReplaces.forEach(({oldStr,newStr})=>{ newShader = newShader.replaceAll(oldStr,newStr) }) return newShader } vs = change(vs,'vs') fs = change(fs,'fs') //console.log('成功替换为webgl2' ) return {vs,fs} }//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth', */ changeShaderToWebgl2(vs, fs, matType, otherReplaces=[]){//部分shader要根据webgl版本作更改 if(!Potree.settings.isWebgl2)return {vs, fs} let turnTo300 = matType != 'ShaderMaterial' && (vs.includes('gl_FragDepthEXT') || fs.includes('gl_FragDepthEXT') ) let addV300 = turnTo300 && matType != 'RawShaderMaterial' // RawShaderMaterial直接material.glslVersion = '300 es' 以加在define之前 let change = (shader, shaderType)=>{ let newShader = shader if(turnTo300){ //非shaderMaterial需要手动改为300 es的写法 addV300 && (newShader = '#version 300 es \n' + newShader) //需要加 #version 300 es。 three.js自带的渲染会自动加所以不用 newShader = this.replaceAll(newShader, 'varying ', shaderType == 'vs' ? 'out ' : 'in ') newShader = this.replaceAll(newShader, 'attribute ', 'in ') if(shaderType == 'fs'){ newShader = this.replaceAll(newShader, 'gl_FragColor', 'fragColor') newShader = newShader.replace('void main', 'out vec4 fragColor;\n void main' )//在void main前加入这个声明 } newShader = this.replaceAll(newShader, 'gl_FragDepthEXT','gl_FragDepth') newShader = this.replaceAll(newShader, 'texture2D','texture') newShader = this.replaceAll(newShader, 'textureCube','texture') } newShader = newShader.replace('#extension GL_EXT_frag_depth : enable','') newShader = this.replaceAll(newShader,'defined(GL_EXT_frag_depth) &&','') otherReplaces.forEach(({oldStr,newStr})=>{ newShader = this.replaceAll(newShader, oldStr, newStr) }) return newShader } vs = change(vs,'vs') fs = change(fs,'fs') //console.log('成功替换为webgl2' ) return {vs,fs} },//three.js的shaderMaterial也有替换功能,搜 '#define gl_FragDepthEXT gl_FragDepth', load16bitPngTex(src,onLoad,onError, pixelFun){//单通道无符号16位的png (正常图片是32位一个像素) const texture = new THREE.DataTexture fetch(src) .then(response => { if(!response.ok){ console.log('loadFile失败' , src ) return onError && onError() } return response.arrayBuffer() }).then(arrayBuffer => { //文件数据 const png = UPNG.decode(arrayBuffer); //解析出像素数据 let pixelCount = png.width * png.height let data = new Uint8Array(pixelCount * 4) let view = new DataView(data.buffer); for(let i=0;i { onError && onError(error) }); return texture } } Potree.Common = Common export default Common