import * as THREE from "../../libs/three.js/build/three.module.js"; import math from "./utils/math.js"; import browser from "./utils/browser.js"; import cameraLight from "./utils/cameraLight.js"; import {Utils} from "../utils.js"; import Common from "./utils/Common.js"; import {BinaryLoader} from "../loader/BinaryLoader.js"; import {Features} from "../Features.js"; import {PointAttribute,PointAttributeTypes} from "../loader/PointAttributes.js"; import {ProfileWindow} from "../viewer/profile.js"; import {XHRFactory} from "../XHRFactory.js"; import {ClipTask, ClipMethod} from "../defines.js"; import {VolumeTool} from "../utils/VolumeTool.js"; import {Box3Helper} from "../utils/Box3Helper.js"; import {KeyCodes} from "../KeyCodes.js"; import {HQSplatRenderer} from "../viewer/HQSplatRenderer.js"; import {LRU} from "../LRU.js"; import {ExtendPointCloudMaterial} from '../materials/ExtendPointCloudMaterial.js' import {PointCloudOctreeGeometry, PointCloudOctreeGeometryNode} from '../PointCloudOctreeGeometry.js' import {Shaders} from "../../build/shaders/shaders.js"; import {LineSegmentsGeometry} from '../../libs/three.js/lines/LineSegmentsGeometry.js' import {LineGeometry} from '../../libs/three.js/lines/LineGeometry.js' import {ExtendView} from '../viewer/ExtendView.js' import {ExtendScene} from '../viewer/ExtendScene.js' KeyCodes.BACKSPACE = 8 //注意,这时候Potree.js中export的内容还不在Potree变量中 var texLoader = new THREE.TextureLoader() texLoader.crossOrigin = "anonymous" {//defines: Potree.defines = {} Potree.defines.Buttons = {// MouseEvent.buttons //buttons,设置按下了鼠标哪些键,是一个3个比特位的二进制值,默认为0。1表示按下主键(通常是左键),2表示按下次要键(通常是右键),4表示按下辅助键(通常是中间的键)。 NONE:0,//add LEFT: 0b0001, RIGHT: 0b0010, MIDDLE: 0b0100 }; /* 如果访问的是button, 用THREE.MOUSE来判断: button,设置按下了哪一个鼠标按键,默认为0。-1表示没有按键,0表示按下主键(通常是左键),1表示按下辅助键(通常是中间的键),2表示按下次要键(通常是右键) */ Potree.browser = browser /////////// add ////////////////////////////////// Potree.defines.GLCubeFaces = { GL_TEXTURE_CUBE_MAP_POSITIVE_X: 0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X: 1, GL_TEXTURE_CUBE_MAP_POSITIVE_Y: 2, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: 3, GL_TEXTURE_CUBE_MAP_POSITIVE_Z: 4, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: 5 }; Potree.defines.PanoSizeClass = { BASE: 1, STANDARD: 2, HIGH: 3, ULTRAHIGH: 4 }; Potree.defines.PanoRendererEvents = { PanoRenderComplete: "panorama.render.complete", TileRenderFailure: "panorama.tile.render.failed", TileRenderSuccess: "panorama.tile.render.success", TileUploadAttempted: "panorama.tile.upload.attempted", UploadAttemptedForAllTiles: "panorama.upload.attempted.all.tiles", ZoomLevelRenderStarted: "panorama.zoom.render.started" }; Potree.defines.SceneRendererEvents = { ContextCreated: "scene-renderer-context-created", AfterRender: "after-render", MemoryUsageUpdated: "scene-renderer-memory-usage-updated" }; Potree.defines.TileDownloaderEvents = { TileDownloadSuccess: "tiledownloader.download.success", TileDownloadFailure: "tiledownloader.download.failure", PanoDownloadComplete: "tiledownloader.pano.download.complete" }; Potree.defines.Vectors = { UP: new THREE.Vector3(0,1,0), DOWN: new THREE.Vector3(0,-1,0), LEFT: new THREE.Vector3(-1,0,0), RIGHT: new THREE.Vector3(1,0,0), FORWARD: new THREE.Vector3(0,0,-1), BACK: new THREE.Vector3(0,0,1) }; Potree.defines.gs3d = { DepthMapRange : 1 << 16, MemoryPageSize : 65536, BytesPerFloat : 4, BytesPerInt : 4, MaxScenes : 32, ProgressiveLoadSectionSize : 262144, ProgressiveLoadSectionDelayDuration : 15, SphericalHarmonics8BitCompressionRange : 3 } Potree.defines.DownloadStatus = Object.freeze({ None: 0, Queued: 1, ForceQueued: 2, Downloading: 3, Downloaded: 4, DownloadFailed: 5 }); Potree.defines.ModelManagerEvents = { ModelAdded: "model-added", ActiveModelChanged: "active-model-changed" }; Potree.defines.PanoramaEvents = { Enter: 'panorama.enter', Exit: 'panorama.exit', LoadComplete: "panorama.load.complete", LoadFailed: "panorama.load.failed", TileLoaded: "panorama.tile.loaded", VideoRendered: "panorama.video.rendered" }; ClipTask.SHOW_INSIDE_Big = 4 } {//Features let gl_, webgl2Support Features.EXT_DEPTH = { isSupported: function (gl) { gl = gl || gl_ gl_ = gl if(browser.detectIOS()){ let {major,minor,patch} = browser.iosVersion() //console.warn('iosVersion',major,minor,patch) if(major == 15 && minor == 4 && patch == 1){ console.warn('检测到是ios15.4.1, 关闭EXT_frag_depth')//该版本ext_depth有问题,导致clear错乱。没有解决办法先关闭。 return false } } return (typeof WebGL2RenderingContext != 'undefined' && gl instanceof WebGL2RenderingContext) || gl.getExtension('EXT_frag_depth'); //shader中的GL_EXT_frag_depth需要判断一下detectIOS吗。。 } } /* Features.getMaxMapSize = (gl)=>{ // 查询最大立方体贴图纹理尺寸 gl = gl || gl_ gl_ = gl let info = { cubeMap: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE), tex: gl.getParameter( gl.MAX_TEXTURE_SIZE ) } if(info.cubeMap < 4096){ console.warn('cubeMap最大仅支持', info.cubeMap) } return info } */ Features.webgl2RealSupport = ()=>{ if(webgl2Support != void 0){ return webgl2Support } let gl try { var canvas = document.createElement('canvas') if(window.WebGL2RenderingContext){ //遇到有设备(iphone8 plus ios14.1 型号MQ8F2CH/A)直接获取webgl2后会点云和全景图闪烁,WebGL2RenderingContext和得到的context是undefined。但是为何4dkk不会闪烁 gl = canvas.getContext('webgl2') //麒麟系统chromium 128 到这一步才获取失败 如果直接对最终的canvas获取webgl2,会造成多viewport无法单独渲染以及clearAlpha透明失败 } }catch (e) { console.log(e) } webgl2Support = !!gl return webgl2Support } } Utils.loadSkybox = function(path, oldSky, callback ) { let camera, scene, parent , cameraOrtho if(!oldSky){ parent = new THREE.Object3D("skybox_root"); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 100000); cameraOrtho = new THREE.OrthographicCamera(-1, 1, 1, -1, Potree.config.view.near, Potree.settings.cameraFar); if(!window.axisYup) camera.up.set(0, 0, 1);//add scene = new THREE.Scene(); let skyboxBgWidth = Potree.config.skyboxBgWidth let skyGeometry = new THREE.BoxBufferGeometry(skyboxBgWidth,skyboxBgWidth,skyboxBgWidth) let skybox = new THREE.Mesh(skyGeometry, new THREE.ShaderMaterial({ vertexShader: Shaders['skybox.vs'], fragmentShader: Shaders['skybox.fs'], side: THREE.BackSide, uniforms:{ tDiffuse: { type: "t", value: null }, matrix:{ type: "m4", value: new THREE.Matrix4 } }, depthTest:false, depthWrite:false }) ); scene.add(skybox); scene.traverse(n => n.frustumCulled = false); // z up //scene.rotation.x = Math.PI / 2; parent.children.push(camera); camera.parent = parent; }else{ camera = oldSky.camera, scene = oldSky.scene parent = oldSky.parent cameraOrtho = oldSky.cameraOrtho } let texture = texLoader.load( path, ()=>{ console.log('loadSkybox成功',path) texture.wrapS = THREE.RepeatWrapping; texture.flipY = false texture.magFilter = THREE.LinearFilter texture.minFilter = THREE.LinearFilter scene.children[0].material.uniforms.tDiffuse.value = texture callback && callback() viewer.dispatchEvent('content_changed') },null,(e)=>{//error console.error('loadSkybox失败',path) }); return {camera, scene, parent, cameraOrtho}; }; Utils.getMousePointCloudIntersection = function(viewport, mouse, pointer, camera, viewer, pointclouds, pickParams = {} ) { //getIntersectByDepthTex /* let result = viewer.edlRenderer.depthTexSampler.sample(viewport, mouse)//add if(result != 'unsupport')return result */ if(!pointclouds || pointclouds.filter(e=>Potree.Utils.getObjVisiByReason(e, 'datasetSelection')).length == 0)return //console.log('getMousePointCloudIntersection') let renderer = viewer.renderer; let resolution = pickParams.resolution || viewport?.resolution || renderer.getSize(new THREE.Vector2) if(pickParams.ifCenter){ pickParams.x = Math.round(resolution.x / 2) pickParams.y = Math.round(resolution.y / 2) }else{ /* if(viewport){ //转换到类似整个画面时 pickParams.x = mouse.x; pickParams.y = viewport.resolution.y - mouse.y; }else{ pickParams.x = mouse.x; pickParams.y = renderer.domElement.clientHeight - mouse.y; } */ pickParams.x = mouse.x; pickParams.y = resolution.y - mouse.y; } //console.log('getMousePointCloudIntersection') /* if(!raycaster){ raycaster = new THREE.Raycaster(); raycaster.setFromCamera(pointer, camera); } */ let raycaster = new THREE.Raycaster(); raycaster.setFromCamera(pointer, camera); let ray = raycaster.ray; let selectedPointcloud = null; let closestDistance = Infinity; let closestIntersection = null; let closestPoint = null; //-----------add-------------------- let old_clipBoxes_in = new Map() let old_clipBoxes_out = new Map() let old_bigClipInBox = new Map() let old_highlightBoxes = new Map() let old_visibleNodes = new Map() //bigClipInBox 最好也写下 let density let sizeType let size = new Map() let visiMap = new Map() let needsUpdate = false; if(pickParams.measuring || Potree.settings.displayMode == 'showPanos') { //测量或无深度图时的全景模式提高精准度. (全景模式有深度图时不会执行到这) density = Potree.settings.pointDensity Potree.settings.pointDensity = 'magnifier' //加载最高level pointclouds.forEach(e=>{//因为全景模式的pointSizeType是fixed所以要还原下 visiMap.set(e,e.visible) e.visible = Potree.Utils.getObjVisiByReason(e, 'datasetSelection'); //先将隐藏的点云显示 if(!e.visible)return size.set(e, e.temp.pointSize) sizeType = e.material.pointSizeType e.material.pointSizeType = Potree.config.material.pointSizeType //e.changePointSize(Potree.config.material.realPointSize*2, true)//更改点云大小到能铺满为止,否则容易识别不到 }) needsUpdate = true }else{ if(viewer.viewports.filter(e=>!e.noPointcloud && e.active).length>1 || pickParams.cameraChanged){//在pick时相机和渲染时不一样的话 viewport.beforeRender && viewport.beforeRender() needsUpdate = true //不updatePointClouds的话hover久了会不准 因node是错的 //但依旧需要camera真的移动到那个位置才能加载出点云 } } if(!pickParams.pickClipped){// 无视clipBoxes for(let pointcloud of pointclouds){ old_clipBoxes_in.set(pointcloud, pointcloud.clipBoxes_in) old_clipBoxes_out.set(pointcloud, pointcloud.clipBoxes_out) old_bigClipInBox.set(pointcloud, pointcloud.bigClipInBox) old_highlightBoxes.set(pointcloud, pointcloud.highlightBoxes) pointcloud.material.setClipBoxes(null, [],[],[]) } needsUpdate = true } if(needsUpdate) { for(let pointcloud of pointclouds){ old_visibleNodes.set(pointcloud, pointcloud.visibleNodes) } if(window.notViewOffset){ Potree.updatePointClouds(pointclouds, camera, viewport.resolution ); }else{ //尽量减少点云加载的范围,集中在pick的空间(这部分以外还是会加载一些散点的) let viewWidth = Math.max(pickParams.pickWindowSize||80, 80) //viewer.magnifier ? viewer.magnifier.viewport.resolution.x : 200 let camera_ = camera.clone() camera_.setViewOffset( resolution.x, resolution.y, pickParams.x-viewWidth/2, ( resolution.y - pickParams.y)-viewWidth/2, viewWidth, viewWidth ); //注意offsetY是从上到下,和一般的不同 Potree.updatePointClouds(pointclouds, camera_, resolution ); } } //------------------------------------------------ let allPointclouds = [] for(let pointcloud of pointclouds){ let point = pointcloud.pick(viewer, viewport, camera, ray, pickParams ); if(!point){ continue; } allPointclouds.push(pointcloud) let distance = camera.position.distanceTo(point.position); if (distance < closestDistance) { closestDistance = distance; selectedPointcloud = pointcloud; closestIntersection = point.position; closestPoint = point; } } //恢复 if(pickParams.measuring || Potree.settings.displayMode == 'showPanos'){ Potree.settings.pointDensity = density pointclouds.forEach(e=>{ if(e.visible){ e.material.pointSizeType = sizeType //e.changePointSize(size.get(e)) } e.visible = visiMap.get(e) }) }else{ /* if(viewer.viewports.filter(e=>!e.noPointcloud).length>1){ viewport.afterRender && viewport.afterRender() } */ } if(!pickParams.pickClipped){//add for(let pointcloud of pointclouds){ pointcloud.material.setClipBoxes(old_bigClipInBox.get(pointcloud), old_clipBoxes_in.get(pointcloud), old_clipBoxes_out.get(pointcloud), old_highlightBoxes.get(pointcloud)) } } if(needsUpdate){ for(let pointcloud of pointclouds){ //不恢复的话(尤其cameraChanged时),在下次render前,再次pick可能是错的。表现为多数据集刚开始reticule消失了,直到ifPointBlockedByIntersect停止 pointcloud.visibleNodes = old_visibleNodes.get(pointcloud) } } if (selectedPointcloud) { let localNormal = closestPoint.normal && new THREE.Vector3().fromArray(closestPoint.normal) return { location: closestIntersection, distance: closestDistance, pointcloud: selectedPointcloud, point: closestPoint, pointclouds: allPointclouds, //add localNormal: localNormal, normal: localNormal?.clone().applyMatrix4(selectedPointcloud.rotateMatrix)//add }; } else { return null; } }; Utils.pixelsArrayToDataUrl = function(pixels, width, height, compressRatio = 0.7) { let canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; let context = canvas.getContext('2d'); pixels = new pixels.constructor(pixels); /* for (let i = 0; i < pixels.length; i++) { pixels[i * 4 + 3] = 255; } */ // flip vertically let bytesPerLine = width * 4; for(let i = 0; i < parseInt(height / 2); i++){ let j = height - i - 1; let lineI = pixels.slice(i * bytesPerLine, i * bytesPerLine + bytesPerLine); let lineJ = pixels.slice(j * bytesPerLine, j * bytesPerLine + bytesPerLine); pixels.set(lineJ, i * bytesPerLine); pixels.set(lineI, j * bytesPerLine); } let imageData = context.createImageData(width, height); imageData.data.set(pixels); context.putImageData(imageData, 0, 0); let dataURL = canvas.toDataURL(compressRatio); return dataURL; } Utils.renderTargetToDataUrl = function(renderTarget, width, height, renderer, compressRatio = 0.7){ let pixelCount = width * height; let buffer = new Uint8Array(4 * pixelCount); renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, buffer); var dataUrl = Utils.pixelsArrayToDataUrl(buffer, width, height, compressRatio) return dataUrl } Utils.mouseToRay = function(pointer, camera ){ let vector = new THREE.Vector3(pointer.x, pointer.y, 1); let origin = new THREE.Vector3(pointer.x, pointer.y, -1); //不能用camera.position,在orbitCamera时不准 vector.unproject(camera); origin.unproject(camera); let direction = new THREE.Vector3().subVectors(vector, origin).normalize(); let ray = new THREE.Ray(origin, direction); return ray; } Utils.getPos2d = function(point, viewport , dom, renderer ){//获取一个三维坐标对应屏幕中的二维坐标 var pos if(math.closeTo(viewport.camera.position, point, 1e-5) ){ //和相机位置重合时显示会四处飘,看是要改成一直显示中间还是隐藏? pos = new THREE.Vector3(0,0,1.5); //1.5是为了不可见 }else{ pos = point.clone().project(viewport.camera) //比之前hotspot的计算方式写得简单 project用于3转2(求法同shader); unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera); } let size = renderer && renderer.getSize(new THREE.Vector2) //如果是渲染到renderTarget上,resolution和dom的大小不一致。如果输出的结果给前端2d用,就使用clinetWidth,如果自己场景用,用renderer.size let w = renderer ? size.x : dom.clientWidth let h = renderer ? size.y : dom.clientHeight var x,y,left,top; x = (pos.x + 1) / 2 * w * viewport.width; y = (1 - (pos.y + 1) / 2) * h * viewport.height; left = viewport.left * w; top = (1- viewport.bottom - viewport.height) * h; var inSight = pos.x <= 1 && pos.x >= -1 //是否在屏幕中 && pos.x <= 1 && pos.y >= -1 return { pos: new THREE.Vector2(left+x,top+y) ,// 屏幕像素坐标 vector: pos, //(范围 -1 ~ 1) trueSide : pos.z<1, //trueSide为false时,即使在屏幕范围内可见,也是反方向的另一个不可以被渲染的点 参见Tag.update inSight : inSight, //在屏幕范围内可见, posInViewport: new THREE.Vector2(x,y), }; } Utils.getPointerPosAtHeight = function(planeZ=0, pointer, camera=viewer.mainViewport.camera){ var origin = new THREE.Vector3(pointer.x, pointer.y, -1).unproject(camera), end = new THREE.Vector3(pointer.x, pointer.y, 1).unproject(camera) var dir = end.sub(origin) let r = (planeZ - origin.z)/dir.z let x = r * dir.x + origin.x let y = r * dir.y + origin.y return {x,y} } Utils.screenPass = new function () { this.screenScene = new THREE.Scene(); this.screenQuad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2, 1)); this.screenQuad.material.depthTest = true; this.screenQuad.material.depthWrite = true; this.screenQuad.material.transparent = true; this.screenScene.add(this.screenQuad); this.camera = new THREE.Camera(); this.render = function (renderer, material, target, composer) { this.screenQuad.material = material; if (typeof target === 'undefined') { (composer || renderer).render(this.screenScene, this.camera); } else { let oldTarget = renderer.getRenderTarget() renderer.setRenderTarget(target); //renderer.clear(); //有时候不能clear,如renderBG后再 (composer || renderer).render(this.screenScene, this.camera); renderer.setRenderTarget(oldTarget) } }; }(); //add Utils.computePointcloudsBound = function(pointclouds){ var boundingBox = new THREE.Box3(); pointclouds.forEach(pointcloud=>{ pointcloud.updateBound() boundingBox.union(pointcloud.bound2) }) var boundSize = boundingBox.getSize(new THREE.Vector3) var center = boundingBox.getCenter(new THREE.Vector3) return {boundSize, center, boundingBox} } Utils.convertScreenPositionToNDC = function(pointer, mouse, width, height) { return pointer = pointer || new THREE.Vector2, pointer.x = mouse.x / width * 2 - 1, pointer.y = 2 * -(mouse.y / height) + 1, pointer } Utils.convertNDCToScreenPosition = function(pointer, mouse, width, height) { return mouse = mouse || new THREE.Vector2, mouse.x = Math.round((pointer.x + 1 ) / 2 * width), mouse.y = Math.round(-(pointer.y - 1 ) / 2 * height), mouse } Utils.getOrthoCameraMoveVec = function(pointerDelta, camera ){//获取当camera为Ortho型时 屏幕点1 到 屏幕点2 的三维距离 let cameraViewWidth = camera.right / camera.zoom let cameraViewHeight = camera.top / camera.zoom let moveVec = new THREE.Vector3; moveVec.set( pointerDelta.x * cameraViewWidth , pointerDelta.y * cameraViewHeight , 0).applyQuaternion(camera.quaternion) return moveVec } Utils.VectorFactory = { fromArray : function(t) { if (t) { if (t.length < 2 || t.length > 3) console.error("Wrong number of ordinates for a point!"); return 3 === t.length ? (new THREE.Vector3).fromArray(t) : (new THREE.Vector2).fromArray(t) } }, fromArray3 : function(t) { if (t) { if (3 !== t.length) console.error("Wrong number of ordinates for a point!"); return (new THREE.Vector3).fromArray(t) } }, fromArray2 : function(t) { if (t) { if (2 !== t.length) console.error("Wrong number of ordinates for a point!"); return (new THREE.Vector2).fromArray(t) } }, toString : function(t) { return t.x.toFixed(8) + "," + t.y.toFixed(8) + "," + t.z.toFixed(3) } } Utils.QuaternionFactory = { rot90 : (new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)), fromArray : function(t) { if (t) { if (4 !== t.length) console.error("Wrong number of ordinates for a quaternion!"); return new THREE.Quaternion(t[1],t[2],t[3],t[0]).multiply(this.rot90) } } , toArray : function(t) { if (t) { var e = t.clone().multiply(a).toArray(); return [e[3], e[0], e[1], e[2]] } } , fromLonLat : function(t) { if (t) return (new THREE.Quaternion).setFromEuler(new THREE.Euler(t.lon,t.lat,0)) } , toLonLat : function(t) { if (t) { var e = (new THREE.Euler).setFromQuaternion(t); return { lon: e.x, lat: e.y } } } } Utils.datasetPosTransform = function(o={}){ let pointcloud = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId) let tranMatrix if(pointcloud){ if(Potree.settings.editType == 'merge'){ tranMatrix = o.fromDataset ? pointcloud.matrixWorld : new THREE.Matrix4().copy(pointcloud.matrixWorld).invert() }else{ tranMatrix = o.fromDataset ? pointcloud.transformMatrix : pointcloud.transformInvMatrix } }else{ if(Potree.settings.intersectOnObjs){ let object = o.object || viewer.objs.children.find(e=>e.dataset_id == o.datasetId) if(object){ tranMatrix = o.fromDataset ? object.matrixWorld : new THREE.Matrix4().copy(object.matrixWorld).invert() } } } if(tranMatrix){ return (new THREE.Vector3).copy(o.position).applyMatrix4(tranMatrix) }else{ if(o.datasetId != void 0){ console.error(`datasetPosTransform找不到datasetId为${o.datasetId}的数据集或模型,请检查数据, 模型未创建或删除`) //很可能是旧的热点,需要删除 } } } Utils.datasetRotTransform = function(o={}){ let object = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId) || o.object || viewer.objs.children.find(e=>e.dataset_id == o.datasetId) if(object){ var matrix, newMatrix, result if(o.rotation){ matrix = new THREE.Matrix4().makeRotationFromEuler(o.rotation) }else if(o.quaternion){ matrix = new THREE.Matrix4().makeRotationFromQuaternion(o.quaternion) }else if(o.matrix){ matrix = o.matrix.clone() }else{ return } let rotateMatrix = o.fromDataset ? object.rotateMatrix : object.rotateInvMatrix if(!rotateMatrix){ rotateMatrix = new THREE.Matrix4().makeRotationFromEuler(object.rotation) //如果是没有漫游点的模型,在此临时获取一个,但和有漫游点的比会有所不同,因为没有初始旋转(如转90度) if(o.toDataset){ rotateMatrix.invert() } } newMatrix = new THREE.Matrix4().multiplyMatrices(rotateMatrix, matrix ) if(o.getRotation){ result = new THREE.Euler().setFromRotationMatrix(newMatrix) }else if(o.getQuaternion){ result = new THREE.Quaternion().setFromRotationMatrix(newMatrix) }else if(o.getMatrix){ result = newMatrix } return result } } Utils.isInsideFrustum = function(bounding, camera){// bounding是否在视野范围内有可见部分(视野就是一个锥状box) let frustumMatrix = new THREE.Matrix4 frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse) let frustum = new THREE.Frustum(); frustum.setFromProjectionMatrix(frustumMatrix) if(bounding instanceof THREE.Sphere){ return frustum.intersectsSphere(bounding) }else{ return frustum.intersectsBox(bounding) } } Utils.isIntersectBox = function(object, boxMatrix){//object是否有在box中的部分。 object可以是点或者bounding, box原为1*1*1,但可能形变 //let frustum = new THREE.Frustum(); //frustum.setFromProjectionMatrix(boxMatrixInverse) --错 let px = new THREE.Vector3(+0.5, 0, 0).applyMatrix4(boxMatrix); let nx = new THREE.Vector3(-0.5, 0, 0).applyMatrix4(boxMatrix); let py = new THREE.Vector3(0, +0.5, 0).applyMatrix4(boxMatrix); let ny = new THREE.Vector3(0, -0.5, 0).applyMatrix4(boxMatrix); let pz = new THREE.Vector3(0, 0, +0.5).applyMatrix4(boxMatrix); let nz = new THREE.Vector3(0, 0, -0.5).applyMatrix4(boxMatrix); let pxN = new THREE.Vector3().subVectors(nx, px).normalize(); let nxN = pxN.clone().multiplyScalar(-1); let pyN = new THREE.Vector3().subVectors(ny, py).normalize(); let nyN = pyN.clone().multiplyScalar(-1); let pzN = new THREE.Vector3().subVectors(nz, pz).normalize(); let nzN = pzN.clone().multiplyScalar(-1); let pxPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(pxN, px); let nxPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(nxN, nx); let pyPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(pyN, py); let nyPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(nyN, ny); let pzPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(pzN, pz); let nzPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(nzN, nz); let frustum = new THREE.Frustum(pxPlane, nxPlane, pyPlane, nyPlane, pzPlane, nzPlane); if(object instanceof THREE.Box3){ var boxBound = new THREE.Box3( new THREE.Vector3(-0.5,-0.5,-0.5), new THREE.Vector3(0.5,0.5,0.5), ).applyMatrix4(boxMatrix) //large boundingbox if(!object.intersectsBox(boxBound))return return frustum.intersectsBox(object) //根据该函数, 若存在某个plane在box上的对应点都在plane背面,则不相交. 可得知在box构成的frustum倾斜时不准确,不相交也判断为相交,甚至不如bound相交准确。所以前面加步骤排除下,但仍不完全准确。(可在裁剪中将box放置到数据集上方旋转下校验) }else if(object instanceof Array){//点合集, 只能粗略计算下 let sphere = new THREE.Sphere() sphere.setFromPoints(object) return this.isIntersectBox(sphere, boxMatrix) }else if(object instanceof THREE.Sphere){ return frustum.intersectsSphere(object) }else if(object instanceof THREE.Vector3){ return frustum.containsPoint(object) }else if(object instanceof THREE.Matrix4){//第一个参数如果和第二个参数一样都是box的worldMatrix } /* containsPoint: ƒ containsPoint( point ) intersectsBox: ƒ intersectsBox( box ) intersectsObject: ƒ intersectsObject( object )//geo intersectsSphere: ƒ intersectsSphere( sphere ) intersectsSprite: ƒ intersectsSprite( sprite ) */ } Utils.getIntersect = function (camera, meshes, pointer, raycaster) { //获取鼠标和meshes交点 if(!raycaster){//getMouseIntersect camera.updateMatrixWorld() raycaster = new THREE.Raycaster() var origin = new THREE.Vector3(pointer.x, pointer.y, -1).unproject(camera), end = new THREE.Vector3(pointer.x, pointer.y, 1).unproject(camera) var dir = end.sub(origin).normalize() raycaster.set(origin, dir) } meshes.forEach(e=>{ raycaster.layers.enable(math.getBaseLog(2,e.layers.mask)) }) var n = raycaster.intersectObjects(meshes) if (0 === n.length) return null return n[0] } Utils.addOrRemoveDefine = function(material, defineName, type, value=''){ let defines = material.defines if(type == 'add'){ if(defines[defineName] != void 0 && defines[defineName] == value)return defines[defineName] = value }else{ if(defines[defineName] == void 0)return; delete defines[defineName] } material.needsUpdate = true; } Utils.makeTexDontResize = function(map){//避免贴图因非2的次方而缩小。小心使用 if(!map || !map.image){ return console.log('!map || !map.image', map, map&&map.image) } if(THREE.Math.isPowerOfTwo(map.image.width ) && THREE.Math.isPowerOfTwo(map.image.height ))return map.wrapS = map.wrapT = THREE.ClampToEdgeWrapping; //原默认 RepeatWrapping map.minFilter = THREE.LinearFilter; // or THREE.NearestFilter 原默认 LinearMipmapLinearFilter map.generateMipmaps = false map.needsUpdate = true } Utils.updateVisible = function(object, reason, ifShow, level=0, type, needRender=true){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的 if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见 if(!object.visibleReasons) object.visibleReasons = []; //在同级时,优先可见 var update = function(){ //先按从高到低的level排列 object.unvisibleReasons = object.unvisibleReasons.sort((a,b)=>b.level-a.level) object.visibleReasons = object.visibleReasons.sort((a,b)=>b.level-a.level) var maxVisiLevel = object.visibleReasons[0] ? object.visibleReasons[0].level : -1 var maxunVisiLevel = object.unvisibleReasons[0] ? object.unvisibleReasons[0].level : -1 var shouldVisi = maxVisiLevel >= maxunVisiLevel var visiBefore = object.visible if(visiBefore != shouldVisi){ object.visible = shouldVisi object.dispatchEvent({ type: 'isVisible', visible: shouldVisi, reason, }) needRender && viewer.dispatchEvent('content_changed') } } if(ifShow){ var index = object.unvisibleReasons.findIndex(e=>e.reason == reason) if(index > -1){ type = 'cancel' object.unvisibleReasons.splice(index, 1); } if(type == 'add' ){ if(!object.visibleReasons.some(e=>e.reason == reason)){ object.visibleReasons.push({reason,level}) } } }else{ var index = object.visibleReasons.findIndex(e=>e.reason == reason) if(index > -1){ type = 'cancel' object.visibleReasons.splice(index, 1); } if(type != 'cancel' ){ if(!object.unvisibleReasons.some(e=>e.reason == reason)){ object.unvisibleReasons.push({reason,level}) } } } update() } /* 复杂案例: 如果物体默认隐藏, 当符合任何一个其他条件时可见,则可: Potree.Utils.updateVisible(this, "default", false, 0 ) //默认隐藏 Potree.Utils.updateVisible(this, 条件名, ifShow, 1, ifShow?'add':'cancel' ) //其他的条件 */ Utils.getObjVisiByReason = function(object,reason){//获取在某条件下是否可见. 注: 用户在数据集选择可不可见为"datasetSelection" if(object.visible)return true else{ return !object.unvisibleReasons || !object.unvisibleReasons.some(e=>e.reason == reason) } } Utils.setCameraLayers = function(camera, enableLayers, extraEnableLayers=[]){//add camera.layers.disableAll() enableLayers.concat(extraEnableLayers).forEach(e=>{ let layer = Potree.config.renderLayers[e] if(layer == void 0){ console.error('setCameraLayer没找到layer!', e); return } camera.layers.enable(layer) }) } Utils.setObjectLayers = function(object, layerName){//add let layer = Potree.config.renderLayers[layerName] if(layer == void 0){ console.error('setObjectLayers没找到layer!',layerName); return } object.traverse(e=>{ e.layers.set(layer) }) } Utils.imgAddText = async (img, text, labelInfo)=>{ let label = new Potree.TextSprite(Object.assign({//如果直接在canvas里写字,要另外写很多和canvas.drawText有关的,所以还是借助textSprite吧 backgroundColor: { r: 0, g: 0, b: 0, a: 0 }, textColor: { r: 255, g: 255, b: 255, a: 1 }, margin:{x:3,y:3}, renderOrder: 50, fontsize : 20, text, },labelInfo)) let labelImg = new Image labelImg.src = label.sprite.material.map.image.toDataURL('image/png') return new Promise((resolve,reject)=>{ labelImg.onload = ()=>{ if(labelInfo.horizonCenter){//水平居中(对img来说) labelInfo.leftRatioToImg = 0.5 - (labelImg.width / img.width)/2 } let result = Common.imgAddLabel(img,labelImg,labelInfo) label.dispose() resolve(result) } }) } Utils.combineImgs = async (imgs, compressRatio, width, height)=>{//拼合图片,顺序从上到下从左到右, 每张图大小不一定一致,但同列的宽一致,同行宽一致 return new Promise((resolve,reject)=>{ let item = imgs[0][0] let wc=imgs.length, hc=imgs[0].length, loadCount = 0, amount = wc * hc width = width || imgs.reduce((w,c)=>w + c[0].width, 0), //相加得到的可能比想要得到的大几个像素,之后会重叠 height = height || imgs[0].reduce((w,c)=>w + c.height, 0) let canvas = document.createElement('canvas') canvas.width = width canvas.height = height let context = canvas.getContext('2d') for(let i=0;i{ loadCount++ context.drawImage(img, img.index.i * img.width, img.index.j * img.height, img.width, img.height) if(loadCount == amount){ var dataUrl = canvas.toDataURL('image/png',compressRatio) context.clearRect(0,0,width,height) resolve(dataUrl) } } } } }) } BinaryLoader.prototype.load = function(node, callback){//解析点云 if (node.loaded) { return; } let url = node.getURL(); if (this.version.equalOrHigher('1.4')) { url += '.bin'; } url += '?m='+node.pcoGeometry.timeStamp //add let xhr = XHRFactory.createXMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; xhr.overrideMimeType('text/plain; charset=x-user-defined'); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if((xhr.status === 200 || xhr.status === 0) && xhr.response !== null){ let buffer = xhr.response; this.parse(node, buffer, callback); } else { node.loadFailed = 'status:'+xhr.status+",url:"+url Potree.numNodesLoading--; //console.error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`); throw new Error(`Failed to load file! HTTP status: ${xhr.status}, file: ${url}`); } } }; try { xhr.send(null); } catch (e) { node.loadFailed = 'catchError' Potree.numNodesLoading--; console.error('加载点云node出错 ', url, e ); } } PointAttribute.RGBA_PACKED = new PointAttribute("rgba", PointAttributeTypes.DATA_TYPE_INT8, 4); PointAttribute.COLOR_PACKED = PointAttribute.RGBA_PACKED; PointAttribute.INTENSITY = new PointAttribute("intensity", PointAttributeTypes.DATA_TYPE_UINT16, 1); PointAttribute.CLASSIFICATION = new PointAttribute("classification", PointAttributeTypes.DATA_TYPE_UINT8, 1); PointAttribute.GPS_TIME = new PointAttribute("gps-time", PointAttributeTypes.DATA_TYPE_DOUBLE, 1); ProfileWindow.prototype.initTHREE = function(){ this.renderer = new THREE.WebGLRenderer({alpha: true, premultipliedAlpha: false}); this.renderer.setClearColor(0x000000, 0); this.renderer.setSize(10, 10); this.renderer.autoClear = false; this.renderArea.append($(this.renderer.domElement)); this.renderer.domElement.tabIndex = '2222'; $(this.renderer.domElement).css('width', '100%'); $(this.renderer.domElement).css('height', '100%'); { let gl = this.renderer.getContext(); if(gl.createVertexArray == null){ let extVAO = gl.getExtension('OES_vertex_array_object'); if(!extVAO){ throw new Error("OES_vertex_array_object extension not supported"); } gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO); gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO); } } this.camera = new THREE.OrthographicCamera(-1000, 1000, 1000, -1000, -1000, 1000); this.camera.up.set(0, 0, 1); this.camera.rotation.order = "ZXY"; this.camera.rotation.x = Math.PI / 2.0; this.scene = new THREE.Scene(); this.profileScene = new THREE.Scene(); let sg = new THREE.SphereGeometry(1, 16, 16); let sm = new THREE.MeshNormalMaterial(); this.pickSphere = new THREE.Mesh(sg, sm); this.scene.add(this.pickSphere); this.viewerPickSphere = new THREE.Mesh(sg, sm); } //Potree_update_visibility Potree.updatePointClouds = function(pointclouds,camera, areaSize ){ viewer.addTimeMark('updateClouds','start') for (let pointcloud of pointclouds) { let start = performance.now(); for (let profileRequest of pointcloud.profileRequests) { profileRequest.update(); let duration = performance.now() - start; if(duration > 5){ break; } } let duration = performance.now() - start; } let result = Potree.updateVisibility(pointclouds, camera, areaSize ); for (let pointcloud of pointclouds) { //pointcloud.updateMaterial(pointcloud.material, pointcloud.visibleNodes, camera, renderer);//转移到渲染时 pointcloud.updateVisibleBounds(); } Potree.lru.freeMemory();//即Potree.lru 能看到所有在加载的node viewer.addTimeMark('updateClouds','end') return result; }; Potree.updateVisibilityStructures = function(pointclouds, camera, areaSize) { let frustums = {}; let camObjPositions = {} let camObjDirs = {} //add let priorityQueue = new BinaryHeap(function (x) { return -x.weight /* 1 / x.weight; */ });//二叉堆。 改,之前的weight不支持负数 viewer.addTimeMark('visiStructure','start') //camera.updateMatrixWorld(); let viewI = camera.matrixWorldInverse; let proj = camera.projectionMatrix; let view = camera.matrixWorld; let projViewI = new THREE.Matrix4().multiply(proj).multiply(viewI) /* let list = pointclouds // stopWhenAllUsed = !viewer.lastFrameChanged let min = 5, max = Math.max(20 , Math.round(list.length / 10 )) let result = Common.batchHandling.getSlice('pcGetFrustum', list, { min,max, durBound1: 3, durBound2: 10} ) //iphonex稳定后大概在7-10。 */ for (let i = 0; i < pointclouds.length; i++) { let pointcloud = pointclouds[i]; if (!pointcloud.initialized()) { continue; } /* let info = history.get(pointcloud) if() */ pointcloud.numVisibleNodes = 0; pointcloud.numVisiblePoints = 0; pointcloud.deepestVisibleLevel = 0; pointcloud.visibleNodes = []; pointcloud.visibleGeometry = []; // 因漫游模式而隐藏的话 依旧需要加入visibleNodes,因为pick需要 /* if (pointcloud.visible && pointcloud.root !== null) { priorityQueue.push({pointcloud: i, node: pointcloud.root, weight: Number.MAX_VALUE}); } */ if (pointcloud.visible || !pointcloud.hasDepthTex && pointcloud.unvisibleReasons && pointcloud.unvisibleReasons.length == 1 && pointcloud.unvisibleReasons[0].reason == 'displayMode' && pointcloud.root !== null) {//改 visible -> priorityQueue.push({pointcloud: i, node: pointcloud.root, weight: Number.MAX_VALUE}); }else{ continue } // frustum in object space let frustum = new THREE.Frustum(); let world = pointcloud.matrixWorld; // use close near plane for frustum intersection /* let frustumCam = camera.clone(); frustumCam.zoom = camera.zoom //add frustumCam.near = Math.min(camera.near, 0.1); frustumCam.updateProjectionMatrix(); */ //----没用到frustumCam,删了 let fm = new THREE.Matrix4().multiply(projViewI).multiply(world); frustum.setFromProjectionMatrix(fm); frustums[i] = frustum //frustums.push(frustum); // camera position in object space let worldI = pointcloud.matrixWorldInverse let camMatrixObject = new THREE.Matrix4().multiply(worldI).multiply(view);//假设点云无变换的话,相机相对于点云的变换矩阵 let camObjPos = new THREE.Vector3().setFromMatrixPosition(camMatrixObject); camObjPositions[i] = camObjPos//camObjPositions.push(camObjPos); let quaternion = new THREE.Quaternion().setFromRotationMatrix(camMatrixObject) let camDir = (new THREE.Vector3(0,0,-1)).applyQuaternion(quaternion) camObjDirs[i] = camDir // hide all previously visible nodes // if(pointcloud.root instanceof PointCloudOctreeNode){ // pointcloud.hideDescendants(pointcloud.root.sceneNode); // } if (pointcloud.root.isTreeNode()) { pointcloud.hideDescendants(pointcloud.root.sceneNode); } for (let j = 0; j < pointcloud.boundingBoxNodes.length; j++) { pointcloud.boundingBoxNodes[j].visible = false; } } viewer.addTimeMark('visiStructure','end') return { 'frustums': frustums, 'camObjPositions': camObjPositions, 'priorityQueue': priorityQueue, camObjDirs }; }; Potree.updateVisibility = function(pointclouds, camera, areaSize){ let numVisibleNodes = 0; let numVisiblePoints = 0; let numVisiblePointsInPointclouds = new Map(pointclouds.map(pc => [pc, 0])); let visibleNodes = []; let visibleGeometry = []; let unloadedGeometry = []; let lowestSpacing = Infinity; // calculate object space frustum and cam pos and setup priority queue let s = Potree.updateVisibilityStructures(pointclouds, camera, areaSize);//得到相机可见范围 let frustums = s.frustums; let camObjPositions = s.camObjPositions; let priorityQueue = s.priorityQueue; let camObjDirs = s.camObjDirs let loadedToGPUThisFrame = 0; let domWidth = areaSize.x; //renderer.domElement.clientWidth; let domHeight = areaSize.y;//renderer.domElement.clientHeight; let fov = (camera.fov * Math.PI) / 180; let slope = Math.tan(fov / 2); let projFactor0 = (0.5 * domHeight) / slope ; // check if pointcloud has been transformed // some code will only be executed if changes have been detected if(!Potree._pointcloudTransformVersion){ Potree._pointcloudTransformVersion = new Map(); } let pointcloudTransformVersion = Potree._pointcloudTransformVersion; for(let pointcloud of pointclouds){ if(pointcloud.hasDepthTex ? !pointcloud.visible : !Potree.Utils.getObjVisiByReason(pointcloud, 'datasetSelection')){//改 visible -> continue; } //if(!pointcloud.visible) continue pointcloud.updateMatrixWorld(); if(!pointcloudTransformVersion.has(pointcloud)){ pointcloudTransformVersion.set(pointcloud, {number: 0, transform: pointcloud.matrixWorld.clone()}); }else{ let version = pointcloudTransformVersion.get(pointcloud); if(!version.transform.equals(pointcloud.matrixWorld)){ version.number++; version.transform.copy(pointcloud.matrixWorld); pointcloud.dispatchEvent({ type: "transformation_changed", target: pointcloud }); } } } while (priorityQueue.size() > 0) { let element = priorityQueue.pop(); //取出权重最大的一个 let node = element.node; let parent = element.parent; let pointcloud = pointclouds[element.pointcloud]; // { // restrict to certain nodes for debugging // let allowedNodes = ["r", "r0", "r4"]; // if(!allowedNodes.includes(node.name)){ // continue; // } // } let box = node.getBoundingBox(); let frustum = frustums[element.pointcloud]; let camObjPos = camObjPositions[element.pointcloud]; if(!frustum) continue //add let camObjDir = camObjDirs[element.pointcloud]; let insideFrustum = frustum.intersectsBox(box); let maxLevel = pointcloud.maxLevel == void 0 ? Infinity : pointcloud.maxLevel; let minLevel = pointcloud.minLevel == void 0 ? 0 : pointcloud.minLevel; //add let level = node.getLevel(); let visible = insideFrustum; visible = visible && !(numVisiblePoints + node.getNumPoints() > Potree.pointBudget); visible = visible && !(numVisiblePointsInPointclouds.get(pointcloud) + node.getNumPoints() > pointcloud.pointBudget); // pointcloud.pointBudget一直是Infinity visible = visible && level <= maxLevel /* && level >= minLevel */ ; //< 改为 <= //visible = visible || node.getLevel() <= 2; let pcWorldInverse = pointcloud.matrixWorld.clone().invert(); /* let m = pcWorldInverse.elements let pcWorldInvM3 = new THREE.Matrix3().set(m[0],m[4],m[12],m[1],m[5],m[13],m[3],m[7],m[15]) //去掉z的 */ //pointcloud.pcMatrix3 = new THREE.Matrix3().set(m[0],m[4],m[12],m[1],m[5],m[13],m[3],m[7],m[15]) //去掉z的 let intersectBox = (clipBox)=>{ let toPCObject = pcWorldInverse.clone().multiply(clipBox.box.matrixWorld); //box乘上点云逆矩阵 return Potree.Utils.isIntersectBox(box, toPCObject) } //改 总共两种box : 可见和不可见(都是并集) let clipBoxes_in = pointcloud.material.clipBoxes_in; let clipBoxes_out = pointcloud.material.clipBoxes_out; let bigClipInBox = pointcloud.material.bigClipInBox if(visible && bigClipInBox){//不在剪裁下载的框内 if(!intersectBox(bigClipInBox)){ visible = false; } } if(visible && clipBoxes_in.length > 0){//当有可见box时,需要在任一可见box内才可见 let visi = false; for(let i = 0, length=clipBoxes_in.length; i < length; i++){ if(intersectBox(clipBoxes_in[i])){ visi = true; break; } } if(!visi){ visible = false } } //outside不做处理。因为node必须完全在clipBox内才能完全隐藏,而这里的intersect只能识别出部分在clipBox内。因而只能说明不在任意一个box内绝对可见,没有意义,这里需要找出不可见的。 if(visible){ let prism = pointcloud.material.activeAttributeName == 'prismHeight' && pointcloud.material.prisms && pointcloud.material.prisms.find(e=>e.computing) if(prism){ let bound = box.clone().applyMatrix4(pointcloud.matrixWorld) if(bound.intersectsBox(prism.prismBound)){ /* //node box是否包含points中的一个点 let box2 = new THREE.Box2().copy(box) let points2d = prisms.points.map(e=>new THREE.Vector2().copy(e).applyMatrix3(pcWorldInvM3)) let intersect = points2d.some(e=>{ return box2.containsPoint(e) }) if(!intersect){ //或者多边形中是否包含node box中的一个点 intersect = [ new THREE.Vector2(box.min.x, box.min.y), new THREE.Vector2(box.max.x, box.max.y), new THREE.Vector2(box.min.x, box.max.y), new THREE.Vector2(box.max.x, box.min.y), ].some(e=>{ if(math.isPointInArea(points2d, null, e) ){ return true } }) //z是不是在外层已经判断好了? if(!intersect){ visible = false } } */ //会有两个互不包含点但是交叉了的情况,所以就不仔细判断了(如横竖两个矩形构成十字架) }else visible = false } } if (node.spacing) { lowestSpacing = Math.min(lowestSpacing, node.spacing); } else if (node.geometryNode && node.geometryNode.spacing) { lowestSpacing = Math.min(lowestSpacing, node.geometryNode.spacing); } if (numVisiblePoints + node.getNumPoints() > Potree.pointBudget) { viewer.dispatchEvent({type:'overPointBudget', restQueueSize: priorityQueue.size(), numVisiblePoints} ) break; } if (!visible) { continue; } // TODO: not used, same as the declaration? // numVisibleNodes++; numVisiblePoints += node.getNumPoints(); let numVisiblePointsInPointcloud = numVisiblePointsInPointclouds.get(pointcloud); numVisiblePointsInPointclouds.set(pointcloud, numVisiblePointsInPointcloud + node.getNumPoints()); pointcloud.numVisibleNodes++; pointcloud.numVisiblePoints += node.getNumPoints(); if (node.isGeometryNode() && (!parent || parent.isTreeNode())) { if (node.isLoaded() && loadedToGPUThisFrame < 2) { node = pointcloud.toTreeNode(node, parent); loadedToGPUThisFrame++; } else { //console.log('unloadedGeometry',node) unloadedGeometry.push({pointcloud,node}); //加载点云。虽然还没加载,但也计入了visibleNodes,只是无children,numPoints=0 visibleGeometry.push(node); } } if (node.isTreeNode()) { Potree.lru.touch(node.geometryNode);//在缓存中计入点云 node.sceneNode.visible = true; node.sceneNode.material = pointcloud.material; /* level >= minLevel && */visibleNodes.push(node); /* level >= minLevel && */pointcloud.visibleNodes.push(node); //if(Potree.settings.sortNodesDis){//add if(pointcloud.material.opacity < 1 && Potree.settings.notAdditiveBlending ){ let nodePos = node.getBoundingSphere().center; let toCam = new THREE.Vector3().subVectors(nodePos, camObjPos) toCam.projectOnVector(camObjDir) node.disSqToCamZ_ = toCam.lengthSq() } if(node._transformVersion === undefined){ node._transformVersion = -1; } let transformVersion = pointcloudTransformVersion.get(pointcloud); if(node._transformVersion !== transformVersion.number){ node.sceneNode.updateMatrix(); //node.sceneNode.matrixWorld.multiplyMatrices(pointcloud.matrixWorld, node.sceneNode.matrix); node.sceneNode.matrixWorld.multiplyMatrices(pointcloud.matrixWorld, node.sceneNode.matrix); node._transformVersion = transformVersion.number; } if (pointcloud.showBoundingBox && !node.boundingBoxNode && node.getBoundingBox) { let colorHue = level / (maxLevel+1) let s = 0.1 + level / (maxLevel+1) let color = (new THREE.Color()).setHSL(colorHue, s, s) let boxHelper = new Box3Helper(node.getBoundingBox(),color); boxHelper.matrixAutoUpdate = false; pointcloud.boundingBoxNodes.push(boxHelper); node.boundingBoxNode = boxHelper; node.boundingBoxNode.matrix.copy(pointcloud.matrixWorld); } else if (pointcloud.showBoundingBox) { node.boundingBoxNode.visible = true; node.boundingBoxNode.matrix.copy(pointcloud.matrixWorld); } else if (!pointcloud.showBoundingBox && node.boundingBoxNode) { node.boundingBoxNode.visible = false; } // if(node.boundingBoxNode !== undefined && exports.debug.allowedNodes !== undefined){ // if(!exports.debug.allowedNodes.includes(node.name)){ // node.boundingBoxNode.visible = false; // } // } } // add child nodes to priorityQueue 由近及远、由大及小逐渐加载 let children = node.getChildren(); for (let i = 0; i < children.length; i++) { let child = children[i]; let weight = 0; if(camera.isPerspectiveCamera){ let sphere = child.getBoundingSphere(); let center = sphere.center; let dd = sphere.center.distanceToSquared(camObjPos); let addPow = 0.2//viewer.mainViewport.view.isFlying() ? 0 : 0.5 //0-0.5,正常原本是0. 数字越大近处加载越快。但会造成远处加载慢甚至因pointBudge限制不加载。 isFlying:漫游时需要尽量加载一下远处的点云 //addPow *= window.devicePixelRatio //devicePixelRatio高的手机需要优先加载最近的高级点云,减少远处的中高级点云。 let distance = Math.pow(dd,0.5+addPow)//Math.sqrt(dd); //提高距离权重,为了提高近处加载速度。 某些场景近处加载慢优化明显,如SS-t-cqCAL6rJ5i //let attenuateDis = 10;//add let radius = sphere.radius; let projFactor = projFactor0 / distance let screenPixelRadius = radius * projFactor; /* if(distance > attenuateDis){ screenPixelRadius -= (distance - attenuateDis) * Math.sqrt(radius) * projFactor0 * 0.002 } */ //screenPixelRadius 和 domHeight 成正比,所以手机横屏后screenPixelRadius会变小。这是正常的,因为vhov不变,相同物体高度在横屏后高度变小,所需要的密度不需要那么高了。但hfov横屏后扩大,所以可见的node范围变大,又增加了一些可见node;只是总体的可见node还是减少了。 //使用hfov和domWidth计算结果相同。 if(screenPixelRadius < pointcloud.minimumNodePixelSize / Math.pow(dd,addPow)){ //理论上因手机像素小,更不容易堆叠铺满,minimumNodePixelSize应该除以window.deviceRatio 但会造成加载过多,而内存小 continue; } weight = screenPixelRadius; if( !sphere.containsPoint(camObjPos) ){ //add 优先加载屏幕中央的点云(手机端缩小离远效果明显,不会那么稀疏) let dir = new THREE.Vector3().subVectors(center, camObjPos).normalize() let cos = 1+dir.dot(camObjDir) //0-2 weight *= cos/2//Math.pow(cos,0.5) //幂越高,旁边的容易加载不到,出现缺块 如SS-t-7DUfWAUZ3V } if(distance - radius < 0){ weight = Number.MAX_VALUE; } //如果能得到每个方向上的密度,也就是node数量,密度大的远处少加载,因为被遮挡了显示也没有意义,就好了。 } else { // TODO ortho visibility //let bb = child.getBoundingBox(); let sphere = child.getBoundingSphere(); //let diagonal = bb.max.clone().sub(bb.min).length(); const reduce = 0 //0-0.5,正常原本是0. if( sphere.radius * /* Math.pow( */camera.zoom/* ,1-reduce) */ < pointcloud.minimumNodePixelSize ){ continue; } let distance = sphere.center.distanceToSquared(camObjPos); //先加载中间然后四周 weight = sphere.radius / distance /* let vec = new THREE.Vector3().subVectors(sphere.center, camObjPos) let disOnCamDir = vec.dot(camObjDir) let vecOnCamDir = camObjDir.clone().multiplyScalar(disOnCamDir) let vecSide = new THREE.Vector3().subVectors(vec, vecOnCamDir) //在屏幕上从中心到该node的向量 let disSide = vecSide.length() //weight = sphere.radius / disSide * camera.zoom - disOnCamDir * 2; //如果用除的,ortho的camera离远了的话dis的影响就小了 weight = sphere.radius / ( disSide * 0.1 + disOnCamDir * 14 ) */ //weight = diagonal; } priorityQueue.push({pointcloud: element.pointcloud, node: child, parent: node, weight: weight}); //貌似好像二叉堆中子节点和父节点没什么关系,就只是为了方便排序层层遍历 } //手机上像素点更小,所以远处感觉会更稀疏 }// end priority queue loop { // update DEM 这是什么 let maxDEMLevel = 4; let candidates = pointclouds.filter(p => (p.generateDEM && p.dem instanceof Potree.DEM)); for (let pointcloud of candidates) { let updatingNodes = pointcloud.visibleNodes.filter(n => n.getLevel() <= maxDEMLevel); pointcloud.dem.update(updatingNodes); } } unloadedGeometry = unloadedGeometry.filter(e=>!e.loadFailed) //过滤加载失败的,否则有失败的就无法发送加载完成 if(unloadedGeometry.length){//加载点云 let idleCount = Common.getBestCountFPS('unloadedGeometry', false, 1, 3) //即使静止,因为加载不仅影响这一帧,所以低fps还是加载少一些 let maxNodesLoading = Common.getBestCount('unloadedGeometry', viewer.lastFrameChanged?1:idleCount, idleCount+4, 3, 14 /* , true */ )//dur在iphoneX中静止有7,pc是2 //!lastFrameChanged静止时加速下载 //THREE.Math.clamp(Math.round(9 - dur), 1, 6 ) //console.log('unloadedGeometry', unloadedGeometry.length) //主要在手机端有效果。不改之前在展示的点云较多时前进会卡。 for (let i = 0; i < Math.min(maxNodesLoading, unloadedGeometry.length); i++) { unloadedGeometry[i].node.load(unloadedGeometry[i].pointcloud.pcoGeometry); } if(!Potree.pointsLoading){ Potree.pointsLoading = true //console.log('startLoad') viewer.dispatchEvent('startLoadPoints') } }else{ if(Potree.pointsLoading){ Potree.pointsLoading = false //console.log('load done!') setTimeout(()=>{ Potree.pointsLoading || viewer.dispatchEvent('pointsLoaded') },document.hidden ? 3000 : 50) //hidden时可能好几秒才更新一次,所以这个并不准 } } Potree.unloadedGeometry = unloadedGeometry //add: Potree.numVisiblePoints = numVisiblePoints Potree.visibleNodes = visibleNodes //if(Potree.settings.sortNodesDis){ //let s = performance.now() for(let pointcloud of pointclouds){ if(pointcloud.material.opacity < 1 && Potree.settings.notAdditiveBlending ){//排序。如果能所有点云一起排序更好,这样遮挡更正确 pointcloud.visibleNodes.sort((a,b)=>{return b.disSqToCamZ_ - a.disSqToCamZ_}) } } //console.log(performance.now() - s) //} return { visibleNodes: visibleNodes, numVisiblePoints: numVisiblePoints, lowestSpacing: lowestSpacing }; }; Potree.numVisiblePoints = 0 /* note: 缓存中的点数 Potree.lru.numPoints 一般会 大于 每个点云显示点总数的numVisiblePoints 当超出缓冲区最大点云数时,加载的点云节点会被dispose彻底消除;否则,隐藏的节点就会等待再次被使用显示 由于加载按照由近及远、由大及小的顺序,要降低卡顿,就只需要降低Potree.pointBudget即可。但目前只设置了三个层次;另外提供maxLevel细节调节,能显示更均匀. 最好多一个调节pointBudge的滑动条 Potree.lru.numPoints Potree.numVisiblePoints viewer.scene.pointclouds[0].visibleNodes.length */ {//HQSplatRenderer let oldInit = HQSplatRenderer.prototype.init; HQSplatRenderer.prototype.init = function(){ oldInit() viewer.addEventListener('resize',this.resize.bind(this)) } HQSplatRenderer.prototype.resize = function(e){ this.rtDepth.setSize(e.canvasWidth, e.canvasHeight); this.rtAttribute.setSize(e.canvasWidth, e.canvasHeight); } HQSplatRenderer.prototype.clear = function(params={}){ this.init(); const {renderer, background} = this.viewer; if(background === "skybox"){ renderer.setClearColor(0x000000, 0); } else if (background === 'gradient') { renderer.setClearColor(0x000000, 0); } else if (background === 'black') { renderer.setClearColor(0x000000, 1); } else if (background === 'white') { renderer.setClearColor(0xFFFFFF, 1); } else { renderer.setClearColor(0x000000, 0); } params.target || renderer.clear(); this.clearTargets(params); } HQSplatRenderer.prototype.render = function(params={}) { this.init(); const viewer = this.viewer; const camera = params.camera ? params.camera : viewer.scene.getActiveCamera(); const {width, height} = params.width ? params : this.viewer.renderer.getSize(new THREE.Vector2()); viewer.renderer.setRenderTarget(params.target||null); viewer.dispatchEvent({type: "render.pass.begin",viewer: viewer}); //params.target || this.resize(width, height); const visiblePointClouds = viewer.scene.pointclouds.filter(pc => pc.visible); const originalMaterials = new Map(); for(let pointcloud of visiblePointClouds){ originalMaterials.set(pointcloud, pointcloud.material); if(!this.attributeMaterials.has(pointcloud)){ let attributeMaterial = new ExtendPointCloudMaterial(); this.attributeMaterials.set(pointcloud, attributeMaterial); } if(!this.depthMaterials.has(pointcloud)){ let depthMaterial = new ExtendPointCloudMaterial(); depthMaterial.setDefine("depth_pass", "#define hq_depth_pass"); depthMaterial.setDefine("use_edl", "#define use_edl"); this.depthMaterials.set(pointcloud, depthMaterial); } } { // DEPTH PASS for (let pointcloud of visiblePointClouds) { let octreeSize = pointcloud.pcoGeometry.boundingBox.getSize(new THREE.Vector3()).x; let material = originalMaterials.get(pointcloud); let depthMaterial = this.depthMaterials.get(pointcloud); depthMaterial.size = material.size; depthMaterial.minSize = material.minSize; depthMaterial.maxSize = material.maxSize; depthMaterial.pointSizeType = material.pointSizeType; depthMaterial.visibleNodesTexture = material.visibleNodesTexture; depthMaterial.weighted = false; depthMaterial.screenWidth = width; depthMaterial.shape = PointShape.CIRCLE; depthMaterial.screenHeight = height; depthMaterial.uniforms.visibleNodes.value = material.visibleNodesTexture; depthMaterial.uniforms.octreeSize.value = octreeSize; depthMaterial.spacing = pointcloud.pcoGeometry.spacing; // * Math.max(...pointcloud.scale.toArray()); depthMaterial.classification = material.classification; depthMaterial.uniforms.classificationLUT.value.image.data = material.uniforms.classificationLUT.value.image.data; depthMaterial.classificationTexture.needsUpdate = true; depthMaterial.uniforms.uFilterReturnNumberRange.value = material.uniforms.uFilterReturnNumberRange.value; depthMaterial.uniforms.uFilterNumberOfReturnsRange.value = material.uniforms.uFilterNumberOfReturnsRange.value; depthMaterial.uniforms.uFilterGPSTimeClipRange.value = material.uniforms.uFilterGPSTimeClipRange.value; depthMaterial.uniforms.uFilterPointSourceIDClipRange.value = material.uniforms.uFilterPointSourceIDClipRange.value; depthMaterial.clipTask = material.clipTask; depthMaterial.clipMethod = material.clipMethod; depthMaterial.setClipBoxes(material.clipBoxes); depthMaterial.setClipPolygons(material.clipPolygons); pointcloud.material = depthMaterial; } viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, (params.rtEDL || this.rtDepth), { clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)), }); } { // ATTRIBUTE PASS for (let pointcloud of visiblePointClouds) { let octreeSize = pointcloud.pcoGeometry.boundingBox.getSize(new THREE.Vector3()).x; let material = originalMaterials.get(pointcloud); let attributeMaterial = this.attributeMaterials.get(pointcloud); attributeMaterial.size = material.size; attributeMaterial.minSize = material.minSize; attributeMaterial.maxSize = material.maxSize; attributeMaterial.pointSizeType = material.pointSizeType; attributeMaterial.activeAttributeName = material.activeAttributeName; attributeMaterial.visibleNodesTexture = material.visibleNodesTexture; attributeMaterial.weighted = true; attributeMaterial.screenWidth = width; attributeMaterial.screenHeight = height; attributeMaterial.shape = PointShape.CIRCLE; attributeMaterial.uniforms.visibleNodes.value = material.visibleNodesTexture; attributeMaterial.uniforms.octreeSize.value = octreeSize; attributeMaterial.spacing = pointcloud.pcoGeometry.spacing; // * Math.max(...pointcloud.scale.toArray()); attributeMaterial.classification = material.classification; attributeMaterial.uniforms.classificationLUT.value.image.data = material.uniforms.classificationLUT.value.image.data; attributeMaterial.classificationTexture.needsUpdate = true; attributeMaterial.uniforms.uFilterReturnNumberRange.value = material.uniforms.uFilterReturnNumberRange.value; attributeMaterial.uniforms.uFilterNumberOfReturnsRange.value = material.uniforms.uFilterNumberOfReturnsRange.value; attributeMaterial.uniforms.uFilterGPSTimeClipRange.value = material.uniforms.uFilterGPSTimeClipRange.value; attributeMaterial.uniforms.uFilterPointSourceIDClipRange.value = material.uniforms.uFilterPointSourceIDClipRange.value; attributeMaterial.elevationGradientRepeat = material.elevationGradientRepeat; attributeMaterial.elevationRange = material.elevationRange; attributeMaterial.gradient = material.gradient; attributeMaterial.matcap = material.matcap; attributeMaterial.intensityRange = material.intensityRange; attributeMaterial.intensityGamma = material.intensityGamma; attributeMaterial.intensityContrast = material.intensityContrast; attributeMaterial.intensityBrightness = material.intensityBrightness; attributeMaterial.rgbGamma = material.rgbGamma; attributeMaterial.rgbContrast = material.rgbContrast; attributeMaterial.rgbBrightness = material.rgbBrightness; attributeMaterial.weightRGB = material.weightRGB; attributeMaterial.weightIntensity = material.weightIntensity; attributeMaterial.weightElevation = material.weightElevation; attributeMaterial.weightRGB = material.weightRGB; attributeMaterial.weightClassification = material.weightClassification; attributeMaterial.weightReturnNumber = material.weightReturnNumber; attributeMaterial.weightSourceID = material.weightSourceID; attributeMaterial.color = material.color; attributeMaterial.clipTask = material.clipTask; attributeMaterial.clipMethod = material.clipMethod; attributeMaterial.setClipBoxes(material.clipBoxes); attributeMaterial.setClipPolygons(material.clipPolygons); pointcloud.material = attributeMaterial; } let gl = this.gl; //viewer.renderer.setRenderTarget(null); viewer.pRenderer.render(viewer.scene.scenePointCloud, camera, this.rtAttribute, { clipSpheres: viewer.scene.volumes.filter(v => (v instanceof SphereVolume)), //material: this.attributeMaterial, blendFunc: [gl.SRC_ALPHA, gl.ONE], //depthTest: false, depthWrite: false }); } for(let [pointcloud, material] of originalMaterials){ pointcloud.material = material; } if(viewer.background === "skybox"){ viewer.renderer.setClearColor(0x000000, 0); viewer.renderer.clear(); viewer.skybox.camera.rotation.copy(viewer.scene.cameraP.rotation); viewer.skybox.camera.fov = viewer.scene.cameraP.fov; viewer.skybox.camera.aspect = viewer.scene.cameraP.aspect; viewer.skybox.parent.rotation.x = 0; viewer.skybox.parent.updateMatrixWorld(); viewer.skybox.camera.updateProjectionMatrix(); viewer.renderer.render(viewer.skybox.scene, viewer.skybox.camera); } else if (viewer.background === 'gradient') { viewer.renderer.setClearColor(0x000000, 0); viewer.renderer.clear(); viewer.renderer.render(viewer.scene.sceneBG, viewer.scene.cameraBG); } else if (viewer.background === 'black') { viewer.renderer.setClearColor(0x000000, 1); viewer.renderer.clear(); } else if (viewer.background === 'white') { viewer.renderer.setClearColor(0xFFFFFF, 1); viewer.renderer.clear(); } else { viewer.renderer.setClearColor(0x000000, 0); viewer.renderer.clear(); } { // NORMALIZATION PASS let normalizationMaterial = this.useEDL ? this.normalizationEDLMaterial : this.normalizationMaterial; if(this.useEDL){ normalizationMaterial.uniforms.edlStrength.value = viewer.edlStrength; normalizationMaterial.uniforms.radius.value = viewer.edlRadius; normalizationMaterial.uniforms.screenWidth.value = width; normalizationMaterial.uniforms.screenHeight.value = height; normalizationMaterial.uniforms.uEDLMap.value = (params.rtEDL || this.rtDepth).texture; } normalizationMaterial.uniforms.uWeightMap.value = this.rtAttribute.texture; normalizationMaterial.uniforms.uDepthMap.value = this.rtAttribute.depthTexture; Utils.screenPass.render(viewer.renderer, normalizationMaterial); } viewer.renderer.render(viewer.scene.scene, camera); viewer.dispatchEvent({type: "render.pass.scene", viewer: viewer}); viewer.renderer.render(viewer.scene.sceneOverlay, camera);// add 透明贴图层 viewer.renderer.clearDepth(); viewer.transformationTool.update(); if(!params.target){ //测量线 viewer.dispatchEvent({type: "render.pass.perspective_overlay",viewer: viewer, camera}); viewer.renderer.render(viewer.overlay, camera);//从 viewer.renderDefault搬过来,为了reticule不遮住测量线 } viewer.renderer.render(viewer.controls.sceneControls, camera); viewer.renderer.render(viewer.clippingTool.sceneVolume, camera); viewer.renderer.render(viewer.transformationTool.scene, camera); viewer.renderer.setViewport(width - viewer.navigationCube.width, height - viewer.navigationCube.width, viewer.navigationCube.width, viewer.navigationCube.width); viewer.renderer.render(viewer.navigationCube, viewer.navigationCube.camera); viewer.renderer.setViewport(0, 0, width, height); viewer.dispatchEvent({type: "render.pass.end",viewer: viewer}); viewer.renderer.setRenderTarget(null) } } //PointCloudOctreeGeometry.js PointCloudOctreeGeometryNode.prototype.loadHierachyThenPoints = function(pointcloud){ let node = this; // load hierarchy let callback = function (node, hbuffer) { let tStart = performance.now(); let view = new DataView(hbuffer); let stack = []; let children = view.getUint8(0); let numPoints = view.getUint32(1, true); node.numPoints = numPoints; stack.push({children: children, numPoints: numPoints, name: node.name}); let decoded = []; let offset = 5; while (stack.length > 0) { let snode = stack.shift(); let mask = 1; for (let i = 0; i < 8; i++) { if ((snode.children & mask) !== 0) { let childName = snode.name + i; let childChildren = view.getUint8(offset); let childNumPoints = view.getUint32(offset + 1, true); stack.push({children: childChildren, numPoints: childNumPoints, name: childName}); decoded.push({children: childChildren, numPoints: childNumPoints, name: childName}); offset += 5; } mask = mask * 2; } if (offset === hbuffer.byteLength) { break; } } // console.log(decoded); let nodes = {}; nodes[node.name] = node; let pco = node.pcoGeometry; let maxLevel_ = 0 for (let i = 0; i < decoded.length; i++) { let name = decoded[i].name; let decodedNumPoints = decoded[i].numPoints; let index = parseInt(name.charAt(name.length - 1)); let parentName = name.substring(0, name.length - 1); let parentNode = nodes[parentName]; let level = name.length - 1; maxLevel_ = Math.max(maxLevel_,level)//add let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index); let currentNode = new PointCloudOctreeGeometryNode(name, pco, boundingBox); currentNode.level = level; currentNode.numPoints = decodedNumPoints; currentNode.hasChildren = decoded[i].children > 0; currentNode.spacing = pco.spacing / Math.pow(2, level); parentNode.addChild(currentNode); nodes[name] = currentNode; } pco.dispatchEvent({type:'updateNodeMaxLevel',level:maxLevel_});//add let duration = performance.now() - tStart; if(duration > 5){ /* let msg = `duration: ${duration}ms, numNodes: ${decoded.length}`; console.log(msg); */ } node.loadPoints(); }; if ((node.level % node.pcoGeometry.hierarchyStepSize) === 0) { // let hurl = node.pcoGeometry.octreeDir + "/../hierarchy/" + node.name + ".hrc"; let hurl = node.pcoGeometry.octreeDir + '/' + node.getHierarchyPath() + '/' + node.name + '.hrc'; hurl += '?m='+node.pcoGeometry.timeStamp //add let xhr = XHRFactory.createXMLHttpRequest(); xhr.open('GET', hurl, true); xhr.responseType = 'arraybuffer'; xhr.overrideMimeType('text/plain; charset=x-user-defined'); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200 || xhr.status === 0) { let hbuffer = xhr.response; callback(node, hbuffer); } else { console.log('Failed to load file! HTTP status: ' + xhr.status + ', file: ' + hurl); Potree.numNodesLoading--; } } }; try { xhr.send(null); } catch (e) { console.log('fehler beim laden der punktwolke: ' + e); } } } PointCloudOctreeGeometryNode.prototype.loadPoints = function(){ let name = this.name this.pcoGeometry.loader.load(this, ()=>{//callback viewer.dispatchEvent('pointcloud_changed' ) //console.log('loadPoints success ', name) }); } //加载点云成功->准备渲染画面->更新点云可见性updateVisibility->请求加载新的点云 PointCloudOctreeGeometryNode.prototype.traverse = function(t, e){//add from navvis 25.js void 0 === e && (e = !0); for (var n, i = e ? [this] : []; void 0 !== (n = i.pop()); ) { t(n); for (var o = 0, r = n.children; o < r.length; o++) { var a = r[o]; null !== a && i.push(a) } } } Object.assign( PointCloudOctreeGeometry.prototype, THREE.EventDispatcher.prototype ); LRU.prototype.freeMemory = function(){ if (this.elements <= 1) { return; } let memoryRatio = browser.isMobile() ? 2 : 5; //改成navvis的,使用pointBudget,否则四屏点云闪烁。 (似乎要比updateVisiblede的node时限制要宽些,作为缓存继续存着。否则会闪烁) let max = THREE.Math.clamp( viewer.viewports.length * memoryRatio * Potree.pointBudget, 0, Potree.settings.maxLRUPoints) for (; this.numPoints > max; ) { var node = this.getLRUItem(); node && this.disposeDescendants(node); } } VolumeTool.prototype.update = function(){} VolumeTool.prototype.startInsertion = function(args = {}){ let volume; if(args.type){ volume = new args.type(); }else{ volume = new Potree.BoxVolume(Object.assign(args,{clip:true}) ); } volume.highlight = true volume.name = args.name || 'Volume-'+args.clipTask; volume.isNew = true viewer.transformObject(null)//先清空 //console.log('startInsertion',volume.uuid) let oldVisiBoxes if(args.clipTask == Potree.ClipTask.SHOW_INSIDE){ //如果是显示类型,需要将所有同类型的解除效果,否则看不到效果。 (或者可以在添加非第一个时去除highlight效果,会更自然,但看不清全貌) oldVisiBoxes = viewer.scene.volumes.filter(v => v.clipTask == Potree.ClipTask.SHOW_INSIDE && !v.highlight ) oldVisiBoxes.forEach(box=>box.highlight = true) } let updatePose = ()=>{ //保证在视野中的大小一致: let camera = this.viewer.scene.getActiveCamera(); let w = math.getScaleForConstantSize({ width2d: 300, camera , position:volume.getWorldPosition(new THREE.Vector3()) , resolution: viewer.mainViewport.resolution//2 }) /* let wp = volume.getWorldPosition(new THREE.Vector3()).applyMatrix4(camera.matrixWorldInverse); // let pp = new THREE.Vector4(wp.x, wp.y, wp.z).applyMatrix4(camera.projectionMatrix); let w = Math.abs((wp.z / 3));*/ if(!isNaN(w))volume.scale.set(w, w, w); {//使水平朝向与camera一致 let direction = viewer.mainViewport.view.direction.setZ(0) volume.quaternion.copy(math.getQuaByAim(direction)) } } this.dispatchEvent({ type: 'start_inserting_volume', volume: volume }); updatePose() this.viewer.scene.addVolume(volume); this.scene.add(volume); let drag = e => { if(e.hoverViewport.name == 'mapViewport')return let I = Utils.getMousePointCloudIntersection( viewer.mainViewport, viewer.inputHandler.mouse, viewer.inputHandler.pointer, this.viewer.scene.getActiveCamera(), this.viewer, this.viewer.scene.pointclouds, {pickClipped: args.clipTask == Potree.ClipTask.SHOW_OUTSIDE } //无视clip状态 ); var worldPos = I && I.location if(!worldPos){ return } volume.position.copy(worldPos); updatePose() }; let cancel = ()=>{ end('remove') } let end = (e) => { if(e.button == THREE.MOUSE.RIGHT && e.pressDistance<=Potree.config.clickMaxDragDis) {//remove e = 'remove' } //console.log('end',volume.uuid, e) if(e != 'remove' && (!e.isAtDomElement || e.pressDistance>Potree.config.clickMaxDragDis))return continueDrag() volume.removeEventListener('drag', drag); volume.removeEventListener('drop', end); this.viewer.removeEventListener('cancel_insertions', cancel); volume.isNew = false viewer.removeEventListener('camera_changed', updatePose) if(e == 'remove'){ viewer.scene.removeVolume(volume); //删除没完成的 }else{ viewer.transformObject(volume) volume.highlight = false } volume.dispatchEvent({type:'createFinish', success:e != 'remove' }) oldVisiBoxes && oldVisiBoxes.forEach(box=>box.highlight = false) }; let continueDrag = ( )=>{ //console.log('continueDrag',volume.uuid ) var timer = setTimeout(()=>{//等 drag=null之后 //右键拖拽结束后需要重新得到drag if(volume.parent && volume.isNew){ viewer.inputHandler.startDragging( volume , {notPressMouse:true} /* {endDragFun: e.drag.endDragFun, notPressMouse:e.drag.notPressMouse, dragViewport:e.drag.dragViewport} */ ) } },1) return timer } volume.addEventListener('drag', drag); volume.addEventListener('drop', end); this.viewer.addEventListener('cancel_insertions', cancel); viewer.addEventListener('camera_changed', updatePose) this.viewer.inputHandler.startDragging(volume, {notPressMouse:true}); return volume; } LineGeometry.prototype.setPositions = function( array ) { //xzw改成类似LineSegments的多段线 (第二个点和第三个点之间是没有线段的, 所以不用在意线段顺序) const points = new Float32Array( array ); LineSegmentsGeometry.prototype.setPositions.call(this, points ); return this; } Object.assign(ExtendView.prototype, THREE.EventDispatcher.prototype) Object.assign(ExtendScene.prototype, THREE.EventDispatcher.prototype );