e.point)
let result = Common.batchHandling.getSlice('shelterByCloud', list, {maxUseCount:maxCloudCount,useEquals:true, stopWhenAllUsed:true} ) //iphonex稳定后大概在7-10。
//list.length>0 && console.log('list',list, maxCloudCount)
result.list.forEach(e=>{
let history = waitCloud2.find(a=>a.point.equals(e))
let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({point:history.point, margin: Potree.config.shelterMargin , pickWindowSize:3, viewport:this.mainViewport} )
if(history.waitCompute.cameraPos){
history.notAtPano = {cameraPos: history.waitCompute.cameraPos , ifShelter }
}else{
history.panos[this.images360.currentPano.id] = ifShelter
}
history.ifShelter = ifShelter
byCloud++
//console.log('补2', history.point.toArray())
delete history.waitCompute
})
}
}
if(byTex || byCloud){
//console.log('shelterComputed',byTex,byCloud, maxTexCount, maxCloudCount)
Common.intervalTool.isWaiting('shelterComputed', ()=>{
//console.log('shelterComputed update')
this.dispatchEvent('shelterComputed')
},340)
}
}
updateDatasetAt(force){//更新所在数据集
let fun = ()=>{
let currPos = viewer.mainViewport.view.position
//if(force || !currPos.equals(this.lastPos)){
//this.lastPos.copy(currPos)
var at = this.scene.pointclouds.filter(e=>
(e.visible || e.unvisibleReasons && e.unvisibleReasons.length == 1 && e.unvisibleReasons[0].reason == 'displayMode')
&& e.ifContainsPoint(currPos)
)
if(Common.getDifferenceSet(at, this.atDatasets).length){
//console.log('atDatasets', at)
this.atDatasets = at
if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
this.updateFpVisiDatasets()
if(!viewer.modules.SiteModel.currentFloor){
this.updatePanosVisibles() //所在数据集的点位显示
}
}
this.dispatchEvent({type:'pointcloudAtChange',pointclouds:at})
}
force = false
}
//}
if(force)fun()
else Common.intervalTool.isWaiting('atWhichDataset', fun , 300)
}
updatePanosVisibles(currentFloor){//显示当前楼层的所有panos
if(!Potree.settings.ifShowMarker)return
let inEntity = currentFloor
//console.error('updatePanosVisibles', currentFloor)
viewer.images360.panos.forEach(pano=>{
let visible = inEntity ? inEntity.panos.includes(pano) : this.atDatasets.some(e=>e.panos.includes(pano))
Potree.Utils.updateVisible(pano, 'buildingChange', visible, 2)
})
this.dispatchEvent('content_changed')
} //注:非official的没有获取sitemodel的信息所以不执行楼层判断,marker显示不对是正常的
/* 2023.11.24 针对部分场景的楼层很矮,很容易就到楼层外了,且不能修改楼高。如SG-t-9NdCpxrUPLL#/,采取如下措施:
1 如果不在任何一楼层,但在某个数据集中,就显示该数据集所有的marker
2 如果当前楼层无,则显示上一次的currentFloor的marker
(目前先使用方案1)
*/
updateMarkerVisibles(){//限制显示的marker个数,因镜头内marker多的时候可能会卡
if(!Potree.settings.ifShowMarker)return
const minRadius = 8 * this.images360.zoomLevel, //当视线垂直于marker时的最小可见距离,此范围内可见的pano绝对可见
maxRadius = 50 * this.images360.zoomLevel, //当视线垂直于marker时的最大可见距离,此范围外绝对不可见
hopeCount = browser.isMobile() ? 8 : 15 //期望达到的真实可见的marker数
let sheltered = (pano)=>{
if(/* Potree.settings.displayMode == 'showPanos' && !Potree.Features.EXT_DEPTH.isSupported() && */this.images360.isAtPano() && !this.mainViewport.view.isFlying()){
return !this.images360.currentPano.neighbours.includes(pano) && this.images360.currentPano != pano //起初因不支持EXT_DEPTH时无法用depthTex遮住marker, 后为了减少绘制,都判断
}
}
let panoMap = new Map //先记录想要设置为可见的
let set = ()=>{//最后确定设置
let count = 0
viewer.images360.panos.forEach(pano=>{
let v = panoMap.get(pano).visible
v && count++
Potree.Utils.updateVisible(pano.marker, 'limitMarkerShow', v )
})
//console.log('updateMarkerVisibles marker显示个数', count)
this.dispatchEvent('content_changed')
}
let isWithinDis = (pano,maxDis)=>{//是否marker到相机的距离 没有超出可视距离。可视距离考虑上倾斜角,倾斜越大可视距离越短
let camPos = viewer.mainViewport.camera.position
let o = panoMap.get(pano)
o.dis = o.dis || camPos.distanceTo(pano.marker.position)
o.sin = o.sin || Math.sqrt(Math.abs(camPos.z - pano.marker.position.z) / o.dis) //和地面夹角的sin。 按公式是不加Math.sqrt的,但是这样大马路上在贴近地面时算出的个数非常少,所以增大点……
return o.dis < maxDis * o.sin
}
viewer.images360.panos.forEach(pano=>{//minRadius内的记录为可见
let o = {}
panoMap.set(pano, o)
if(pano.visible && !sheltered(pano) && isWithinDis(pano, minRadius)){
o.visible = true;
}
})
//不超过hopeCount的话,可以直接确定设置
if(viewer.images360.panos.filter(pano=> panoMap.get(pano).visible ).length >= hopeCount)return set()
//距离超过maxRadius就绝对不可见
let insideOutCirle = viewer.images360.panos.filter(pano=> pano.visible && !sheltered(pano) && isWithinDis(pano, maxRadius))
if(insideOutCirle.length <= hopeCount){
insideOutCirle.forEach(pano=>panoMap.get(pano).visible = true )
return set()
}
//数量超过hopeCount时,根据距离排序
insideOutCirle.sort((a,b)=>{return panoMap.get(a).dis - panoMap.get(b).dis })
let slice = insideOutCirle.slice(0,hopeCount)
slice.forEach(pano=>panoMap.get(pano).visible = true )
set()
}
updateFpVisiDatasets(){
let Clip = this.modules.Clip
let SiteModel = this.modules.SiteModel
let Alignment = this.modules.Alignment
var currentFloor = SiteModel.currentFloor;
/* if(Clip.editing){//下载页面已经改为和普通时一样,根据位置判断
this.updateCadVisibles(Clip.selectedDatasets)
}else */if(this.selectedFloorplan){//平面图设置中 或 地理设置
let pointclouds = [this.selectedFloorplan]
this.updateCadVisibles(pointclouds)
}else if(SiteModel.editing || Alignment.editing){//只显示勾选的,也就是显示的点云的
let pointclouds = this.scene.pointclouds.filter(p => Potree.Utils.getObjVisiByReason(p,'datasetSelection') );
this.updateCadVisibles(pointclouds)
//this.updatePanosVisibles(currentFloor/* , pointclouds */)
}else{
let pointclouds = currentFloor ? this.findPointcloudsAtFloor(currentFloor) : []
if(pointclouds.length == 0){
if(this.focusDatasets){
pointclouds = this.focusDatasets
}
}
if(pointclouds.length == 0){//如果当前不在任何楼层或楼层中无数据集,就用当前所在数据集
pointclouds = this.atDatasets
}
this.updateCadVisibles(pointclouds)
//this.updatePanosVisibles(currentFloor/* , pointclouds */)
}
}
/* findPointcloudsAtFloor(entity){//找当前楼层需要显示哪些数据集。
//数据集的belongToEntity 在这个entity内(否则会出现点击数据集飞过去平面图却不显示)。or 如果数据集有漫游点的话,需要包含>20%的漫游点。 (防止重叠体积很大但其实一个漫游点都不包含)
//重叠体积>50% 或 包含>50%的漫游点
const ratio1 = 0.2, ratio2 = 0.5, ratio3 = 0.95
var lowScores = []
var pointclouds = viewer.scene.pointclouds.filter(e=>{
let score = 0
if(e.belongToEntity && (e.belongToEntity == entity || e.belongToEntity.buildParent == entity)){//条件1 若该数据集挂载到该楼层 或 该数据集挂载到的房间属于该楼层(这样能显示该层所有房间)
return true
}
if(e.panos.length){//条件2
var insidePanos = e.panos.filter(a=>entity.ifContainsPoint(a.position));
let panoCountRatio = insidePanos.length / e.panos.length
if(panoCountRatio > ratio2)return true
score += panoCountRatio
}
//条件3
let volume = entity.intersectPointcloudVolume(e);
let volumeRatio = volume / entity.getVolume(true) //注:hole加入计算
if(volumeRatio > ratio3){ //ratio3要高一些,因为点云bounding可能很大,包含很多无点云的空间。即使整个数据集包含entity都不一定看起来在数据集中。(千万要防止两层楼都显示了)
return true
}else{
score += volumeRatio
}
lowScores.push({score, pointcloud:e})
})
if(pointclouds.length == 0){//从低分项挑一个出来。
lowScores.sort((a,b)=>{return a.score - b.score})
if(lowScores[0].score > 0.4){
pointclouds = [lowScores[0].pointcloud]
}
}
return pointclouds
} */
findPointcloudsAtFloor(entity){//找当前楼层需要显示哪些数据集。
//数据集的belongToEntity 在这个entity内(否则会出现点击数据集飞过去平面图却不显示)。or 如果数据集有漫游点的话,需要包含>20%的漫游点。 (防止重叠体积很大但其实一个漫游点都不包含)
//重叠体积>50% 或 包含>50%的漫游点
const ratio1 = 0.2, ratio2 = 0.5, ratio3 = 0.95
var lowScores = []
var pointclouds = viewer.scene.pointclouds.filter(e=>{
let score = 0
if(e.belongToEntity && (e.belongToEntity == entity || e.belongToEntity.buildParent == entity)){//条件1 若该数据集挂载到该楼层 或 该数据集挂载到的房间属于该楼层(这样能显示该层所有房间)
return true
}
if(e.panos.length){//条件2
var insidePanos = e.panos.filter(a=>entity.ifContainsPoint(a.position));
let panoCountRatio = insidePanos.length / e.panos.length
if(panoCountRatio > ratio2)return true
score += panoCountRatio * 2
}
//条件3
let coverHeightRatio = entity.coverPointcloudHeight(e,true)//重叠高度占楼层高度的比率。
if(coverHeightRatio < 0.2) return
let {toEntity,toPointcloud} = entity.intersectPointcloudArea(e,true); //重叠面积占比
let coverAreaRatio = Math.max(toEntity,toPointcloud);//占数据集俯视面积的比率 和 占楼层的面积比率,挑一个大的。 (有的数据集被划分多个楼层,所以每层都应该显示该数据集的图。还有的数据集比较大,可能包含多个建筑,则看占楼层面积)
if(coverAreaRatio < 0.2) return
score += coverAreaRatio * coverHeightRatio
lowScores.push({score, pointcloud:e})
})
/* if(pointclouds.length == 0){//从低分项挑一个出来。
lowScores.sort((a,b)=>{return a.score - b.score})
if(lowScores[0].score > 0.4){
pointclouds = [lowScores[0].pointcloud]
}
} */
lowScores.forEach(e=>{//一个楼层里可以包含许多个数据集 调试SG-t-ds27ym7xzjJ
if(e.score > 0.5){
pointclouds.push(e.pointcloud)
}
})
return pointclouds
}
updateCadVisibles(visiClouds, force){
//console.log('visiClouds',visiClouds)
let oldVisi = this.fpVisiDatasets
var visiClouds = this.fpVisiDatasets = visiClouds
if(!force){
var difference = Common.getDifferenceSet(oldVisi , visiClouds)
if(difference.length == 0)return
}
//console.log('visiClouds',visiClouds.map(e=>e.name))
viewer.scene.pointclouds.forEach(pointcloud=>{
var floorplan = viewer.mapViewer.mapLayer.getFloorplan(pointcloud.dataset_id)
var visi = visiClouds.includes(pointcloud)
if(floorplan){
Potree.Utils.updateVisible(floorplan.objectGroup, 'buildingChange', visi)
}/* else if(!visi){
let changeVisi = (e)=>{
Potree.Utils.updateVisible(e.floorplan.objectGroup, 'buildingChange', this.fpVisiDatasets.includes(pointcloud))
viewer.mapViewer.mapLayer.removeEventListener('floorplanLoaded', changeVisi)
console.log('updateCadVisibles加载后更改显示',e)
}
viewer.mapViewer.mapLayer.addEventListener('floorplanLoaded', changeVisi)
} */
//已经添加了全局的 floorplanLoaded后会updateCadVisibles,这段就删了
})
viewer.mapViewer.mapLayer.needUpdate = true //可能需要更新加载的level程度
viewer.mapViewer.needRender = true //若上句不触发加载也要立即重新绘制
}
//促使点云加载出最高级别
testPointcloudsMaxLevel(){ //所有点云都无需testMaxNodeLevel 就停止
let camera_changed, count = 0
let test = (e={})=>{
camera_changed = true
let camera = e.camera || this.scene.getActiveCamera()
Common.intervalTool.isWaiting('testPointcloudsMaxLevel', ()=>{
if(!camera_changed && count>50 || Potree.settings.displayMode == 'showPanos' || this.pauseTestMaxLevel )return //只有当camera_changed后才继续循环, 除了最开始几次需要连续加载下
camera_changed = false
count ++;
//console.log('testPointcloudsMaxLevel中', camera.type /* count */)
let oldCount = this.testMaxNodeCount
var success = true
viewer.scene.pointclouds.forEach(e=>{
var wait = e.testMaxNodeLevel(camera)
if(wait){
success = false;
}
})
/* if(oldCount<=Potree.config.testNodeCount1 && this.testMaxNodeCount>Potree.config.testNodeCount1 ){//差不多等当前所在数据集nodeMaxLevel加载出来
viewer.scene.pointclouds.forEach(e=>{e.changePointSize()}) //重新更新一下大小。因之前用的是nodeMaxLevelPredict (防止刚开始因nodeMaxLevel没涨完,导致过大的点云突然出现
} */
if(!success)return true //没有全部加载完,继续循环
else {
this.removeEventListener('camera_changed',test)
console.log('testPointcloudsMaxLevel结束')
}
}, count<10 ? 250 : 500)
}
this.addEventListener('camera_changed',test)
test()
/* 检验:
viewer.scene.pointclouds.sort((a,b)=>a.nodeMaxLevelPredict.min - b.nodeMaxLevelPredict.min).forEach(e=>console.log(e.nodeMaxLevel, e.nodeMaxLevelPredict.min))
*/
}
setPointLevels(){
this.scene.pointclouds.forEach(e=>{
e.setPointLevel()
})
}
onCrash(error){
console.error(error)
$(this.renderArea).empty();
if ($(this.renderArea).find('#potree_failpage').length === 0) {
let elFailPage = $(`
Potree Encountered An Error
This may happen if your browser or graphics card is not supported.
We recommend to use
Chrome
or
Firefox.
Please also visit webglreport.com and
check whether your system supports WebGL.
If you are already using one of the recommended browsers and WebGL is enabled,
consider filing an issue report at github,
including your operating system, graphics card, browser and browser version, as well as the
error message below.
Please do not report errors on unsupported browsers.
`);
let elErrorMessage = elFailPage.find('#potree_error_console');
elErrorMessage.html(error.stack);
$(this.renderArea).append(elFailPage);
}
throw error;
}
// ------------------------------------------------------------------------------------
// Viewer API
// ------------------------------------------------------------------------------------
setScene (scene) {
if (scene === this.scene) {
return;
}
let oldScene = this.scene;
this.scene = scene;
this.dispatchEvent({
type: 'scene_changed',
oldScene: oldScene,
scene: scene
});
{ // Annotations
$('.annotation').detach();
// for(let annotation of this.scene.annotations){
// this.renderArea.appendChild(annotation.domElement[0]);
// }
this.scene.annotations.traverse(annotation => {
this.renderArea.appendChild(annotation.domElement[0]);
});
if (!this.onAnnotationAdded) {
this.onAnnotationAdded = e => {
// console.log("annotation added: " + e.annotation.title);
e.annotation.traverse(node => {
$("#potree_annotation_container").append(node.domElement);
//this.renderArea.appendChild(node.domElement[0]);
node.scene = this.scene;
});
};
}
if (oldScene) {
oldScene.annotations.removeEventListener('annotation_added', this.onAnnotationAdded);
}
this.scene.annotations.addEventListener('annotation_added', this.onAnnotationAdded);
}
};
setControls(controls/* , setSpeed */){
if (controls !== this.controls) {
if (this.controls) {
this.controls.setEnable(false)
//this.inputHandler.removeInputListener(this.controls);
this.controls.moveSpeed = this.moveSpeed; //记录 (因为orbit的radius很大,转为firstPerson时要缩小)
}
this.controls = controls;
controls.moveSpeed && this.setMoveSpeed(controls.moveSpeed) //add
this.controls.setEnable(true)
//this.inputHandler.addInputListener(this.controls);
}
}
getControls () {
if(this.renderer.xr.isPresenting){
return this.vrControls;
}else{
return this.controls;
}
}
getMinNodeSize () {
return this.minNodeSize;
};
setMinNodeSize (value) {
if (this.minNodeSize !== value) {
this.minNodeSize = value;
this.dispatchEvent({'type': 'minnodesize_changed', 'viewer': this});
}
};
getBackground () {
return this.background;
}
setBackground(bg, src){
/* if (this.background === bg ) {
return;
} */
if(bg === "skybox"){
if(!src)src = Potree.resourcePath+'/textures/skybox/xingkong.jpg'
this.skybox = Utils.loadSkybox(src, this.skybox);
}
this.background = bg;
this.backgroundOpacity = 1//add
this.dispatchEvent({'type': 'background_changed', 'viewer': this});
}
setDescription (value) {
this.description = value;
$('#potree_description').html(value);
//$('#potree_description').text(value);
}
getDescription(){
return this.description;
}
setShowBoundingBox (value) {
if (this.showBoundingBox !== value) {
this.showBoundingBox = value;
this.dispatchEvent({'type': 'show_boundingbox_changed', 'viewer': this});
}
};
getShowBoundingBox () {
return this.showBoundingBox;
};
setMoveSpeed (value) {
if (this.getMoveSpeed() !== value) {
this.mainViewport.setMoveSpeed(value)
this.dispatchEvent({'type': 'move_speed_changed', 'viewer': this, 'speed': value});
}
};
getMoveSpeed () {
return this.mainViewport.moveSpeed;
};
setWeightClassification (w) {
for (let i = 0; i < this.scene.pointclouds.length; i++) {
this.scene.pointclouds[i].material.weightClassification = w;
this.dispatchEvent({'type': 'attribute_weights_changed' + i, 'viewer': this});
}
};
setFreeze (value) {
value = Boolean(value);
if (this.freeze !== value) {
this.freeze = value;
this.dispatchEvent({'type': 'freeze_changed', 'viewer': this});
}
};
getFreeze () {
return this.freeze;
};
setElevationGradientRepeat(value){
if(this.elevationGradientRepeat !== value){
this.elevationGradientRepeat = value;
this.dispatchEvent({
type: "elevation_gradient_repeat_changed",
viewer: this});
}
}
setPointBudget (value) { //pointBudget: 每次刷新显示点数量的最大值。 缓存中的点数量也跟此有关,但大于这个数值。
if (Potree.pointBudget !== value && value) {
Potree.pointBudget = parseInt(value);
this.dispatchEvent({'type': 'point_budget_changed', 'viewer': this});
}
};
getPointBudget () {
return Potree.pointBudget;
};
setShowAnnotations (value) {
if (this.showAnnotations !== value) {
this.showAnnotations = value;
this.dispatchEvent({'type': 'show_annotations_changed', 'viewer': this});
}
}
getShowAnnotations () {
return this.showAnnotations;
}
setDEMCollisionsEnabled(value){
if(this.useDEMCollisions !== value){
this.useDEMCollisions = value;
this.dispatchEvent({'type': 'use_demcollisions_changed', 'viewer': this});
};
};
getDEMCollisionsEnabled () {
return this.useDEMCollisions;
};
setEDLEnabled (value) {
value = Boolean(value) && Features.SHADER_EDL.isSupported();
if (this.useEDL !== value) {
this.useEDL = value;
this.dispatchEvent({'type': 'use_edl_changed', 'viewer': this});
this.dispatchEvent('pointcloud_changed')
}
};
getEDLEnabled () {
return this.useEDL;
};
setEDLRadius (value) {
if (this.edlRadius !== value) {
this.edlRadius = value;
this.dispatchEvent({'type': 'edl_radius_changed', 'viewer': this});
this.dispatchEvent('pointcloud_changed')
}
};
getEDLRadius () {
return this.edlRadius;
};
setEDLStrength (value) {
if (this.edlStrength !== value) {
this.edlStrength = value;
this.dispatchEvent({'type': 'edl_strength_changed', 'viewer': this});
this.dispatchEvent('pointcloud_changed')
}
};
getEDLStrength () {
return this.edlStrength;
};
setEDLOpacity (value) {
if (this.edlOpacity !== value) {
this.edlOpacity = value;
this.dispatchEvent({'type': 'edl_opacity_changed', 'viewer': this});
this.dispatchEvent('pointcloud_changed')
}
};
getEDLOpacity () {
return this.edlOpacity;
};
setFOV (value) {
if (this.fov !== value) {
let oldFov = this.fov
this.fov = value;
this.scene.cameraP.fov = this.fov; //add
this.scene.cameraP.updateProjectionMatrix() //add
this.dispatchEvent({'type': 'fov_changed', 'viewer': this, oldFov, fov:this.fov});
}
};
getFOV () {
return this.fov;
};
disableAnnotations () {
this.scene.annotations.traverse(annotation => {
annotation.domElement.css('pointer-events', 'none');
// return annotation.visible;
});
};
enableAnnotations () {
this.scene.annotations.traverse(annotation => {
annotation.domElement.css('pointer-events', 'auto');
// return annotation.visible;
});
}
setClassifications(classifications){
this.classifications = classifications;
this.dispatchEvent({'type': 'classifications_changed', 'viewer': this});
}
setClassificationVisibility (key, value) {
if (!this.classifications[key]) {
this.classifications[key] = {visible: value, name: 'no name'};
this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this});
} else if (this.classifications[key].visible !== value) {
this.classifications[key].visible = value;
this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this});
}
}
toggleAllClassificationsVisibility(){
let numVisible = 0;
let numItems = 0;
for(const key of Object.keys(this.classifications)){
if(this.classifications[key].visible){
numVisible++;
}
numItems++;
}
let visible = true;
if(numVisible === numItems){
visible = false;
}
let somethingChanged = false;
for(const key of Object.keys(this.classifications)){
if(this.classifications[key].visible !== visible){
this.classifications[key].visible = visible;
somethingChanged = true;
}
}
if(somethingChanged){
this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this});
}
}
setFilterReturnNumberRange(from, to){
this.filterReturnNumberRange = [from, to];
this.dispatchEvent({'type': 'filter_return_number_range_changed', 'viewer': this});
}
setFilterNumberOfReturnsRange(from, to){
this.filterNumberOfReturnsRange = [from, to];
this.dispatchEvent({'type': 'filter_number_of_returns_range_changed', 'viewer': this});
}
setFilterGPSTimeRange(from, to){
this.filterGPSTimeRange = [from, to];
this.dispatchEvent({'type': 'filter_gps_time_range_changed', 'viewer': this});
}
setFilterPointSourceIDRange(from, to){
this.filterPointSourceIDRange = [from, to]
this.dispatchEvent({'type': 'filter_point_source_id_range_changed', 'viewer': this});
}
setLengthUnit (value) {
switch (value) {
case 'm':
this.lengthUnit = LengthUnits.METER;
this.lengthUnitDisplay = LengthUnits.METER;
break;
case 'ft':
this.lengthUnit = LengthUnits.FEET;
this.lengthUnitDisplay = LengthUnits.FEET;
break;
case 'in':
this.lengthUnit = LengthUnits.INCH;
this.lengthUnitDisplay = LengthUnits.INCH;
break;
}
this.dispatchEvent({ 'type': 'length_unit_changed', 'viewer': this, value: value});
};
setLengthUnitAndDisplayUnit(lengthUnitValue, lengthUnitDisplayValue) {
switch (lengthUnitValue) {
case 'm':
this.lengthUnit = LengthUnits.METER;
break;
case 'ft':
this.lengthUnit = LengthUnits.FEET;
break;
case 'in':
this.lengthUnit = LengthUnits.INCH;
break;
}
switch (lengthUnitDisplayValue) {
case 'm':
this.lengthUnitDisplay = LengthUnits.METER;
break;
case 'ft':
this.lengthUnitDisplay = LengthUnits.FEET;
break;
case 'in':
this.lengthUnitDisplay = LengthUnits.INCH;
break;
}
this.dispatchEvent({ 'type': 'length_unit_changed', 'viewer': this, value: lengthUnitValue });
};
zoomTo(node, factor, animationDuration = 0){
let view = this.mainViewport.view;
let camera = this.scene.cameraP.clone();
camera.rotation.copy(this.scene.cameraP.rotation);
camera.rotation.order = "ZXY";
camera.rotation.x = Math.PI / 2 + view.pitch;
camera.rotation.z = view.yaw;
camera.updateMatrix();
camera.updateMatrixWorld();
camera.zoomTo(node, factor);
let bs;
if (node.boundingSphere) {
bs = node.boundingSphere;
} else if (node.geometry && node.geometry.boundingSphere) {
bs = node.geometry.boundingSphere;
} else {
bs = node.boundingBox.getBoundingSphere(new THREE.Sphere());
}
bs = bs.clone().applyMatrix4(node.matrixWorld);
let startPosition = view.position.clone();
let endPosition = camera.position.clone();
let startTarget = view.getPivot();
let endTarget = bs.center;
let startRadius = view.radius;
let endRadius = endPosition.distanceTo(endTarget);
let easing = TWEEN.Easing.Quartic.Out;
{ // animate camera position
let pos = startPosition.clone();
let tween = new TWEEN.Tween(pos).to(endPosition, animationDuration);
tween.easing(easing);
tween.onUpdate(() => {
view.position.copy(pos);
});
tween.start();
}
{ // animate camera target
let target = startTarget.clone();
let tween = new TWEEN.Tween(target).to(endTarget, animationDuration);
tween.easing(easing);
tween.onUpdate(() => {
view.lookAt(target);
});
tween.onComplete(() => {
view.lookAt(target);
this.dispatchEvent({type: 'focusing_finished', target: this});
});
this.dispatchEvent({type: 'focusing_started', target: this});
tween.start();
}
};
moveToGpsTimeVicinity(time){
const result = Potree.Utils.findClosestGpsTime(time, viewer);
const box = result.node.pointcloud.deepestNodeAt(result.position).getBoundingBox();
const diameter = box.min.distanceTo(box.max);
const camera = this.scene.getActiveCamera();
const offset = camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(diameter);
const newCamPos = result.position.clone().sub(offset);
this.scene.view.position.copy(newCamPos);
this.scene.view.lookAt(result.position);
}
showAbout () {
$(function () {
$('#about-panel').dialog();
});
};
getGpsTimeExtent(){
const range = [Infinity, -Infinity];
for(const pointcloud of this.scene.pointclouds){
const attributes = pointcloud.pcoGeometry.pointAttributes.attributes;
const aGpsTime = attributes.find(a => a.name === "gps-time");
if(aGpsTime){
range[0] = Math.min(range[0], aGpsTime.range[0]);
range[1] = Math.max(range[1], aGpsTime.range[1]);
}
}
return range;
}
fitToScreen (factor = 1, animationDuration = 0) {
let box = this.getBoundingBox(this.scene.pointclouds);
let node = new THREE.Object3D();
node.boundingBox = box;
this.zoomTo(node, factor, animationDuration);
this.controls.stop();
};
toggleNavigationCube() {
this.navigationCube.visible = !this.navigationCube.visible;
/* this.viewports.push({
name:'navigationCube',
left:0, bottom:0, width:0.2,
}) */
}
/* setView(pos, view) {
if(!pos) return;
switch(pos) {
case "F":
this.setFrontView(view);
break;
case "B":
this.setBackView(view);
break;
case "L":
this.setLeftView(view);
break;
case "R":
this.setRightView(view);
break;
case "U":
this.setTopView(view);
break;
case "D":
this.setBottomView(view);
break;
}
} */
setTopView(view){
view = view || this.scene.view
view.setCubeView("top")
this.fitToScreen();
};
setBottomView(){
this.scene.view.yaw = -Math.PI;
this.scene.view.pitch = Math.PI / 2;
this.fitToScreen();
};
setFrontView(view){
view = view || this.scene.view
view.yaw = 0;
view.pitch = 0;
this.fitToScreen();
};
setBackView(view){
view = view || this.scene.view
view.yaw = Math.PI;
view.pitch = 0;
this.fitToScreen();
};
setLeftView(){
this.scene.view.yaw = -Math.PI / 2;
this.scene.view.pitch = 0;
this.fitToScreen();
};
setRightView () {
this.scene.view.yaw = Math.PI / 2;
this.scene.view.pitch = 0;
this.fitToScreen();
};
flipYZ () {
this.isFlipYZ = !this.isFlipYZ;
// TODO flipyz
console.log('TODO');
}
setCameraMode(mode){
this.scene.cameraMode = mode;
viewer.mainViewport.camera = mode == CameraMode.PERSPECTIVE ? this.scene.cameraP : this.scene.cameraO; //改
for(let pointcloud of this.scene.pointclouds) {
pointcloud.material.useOrthographicCamera = mode == CameraMode.ORTHOGRAPHIC;
}
}
getProjection(){
const pointcloud = this.scene.pointclouds[0];
if(pointcloud){
return pointcloud.projection;
}else{
return null;
}
}
async loadProject(url,done){
const response = await fetch(url);
if(response.ok){
const text = await response.text();
const json = JSON5.parse(text);
// const json = JSON.parse(text);
if(json.type === "Potree"){
Potree.loadProject(viewer, json, done);
}
}else{
console.warn("未能加载:"+url )
}
}
saveProject(){
return Potree.saveProject(this);
}
loadSettingsFromURL(){
if(Utils.getParameterByName("pointSize")){
this.setPointSize(parseFloat(Utils.getParameterByName("pointSize")));
}
if(Utils.getParameterByName("FOV")){
this.setFOV(parseFloat(Utils.getParameterByName("FOV")));
}
if(Utils.getParameterByName("opacity")){
this.setOpacity(parseFloat(Utils.getParameterByName("opacity")));
}
if(Utils.getParameterByName("edlEnabled")){
let enabled = Utils.getParameterByName("edlEnabled") === "true";
this.setEDLEnabled(enabled);
}
if (Utils.getParameterByName('edlRadius')) {
this.setEDLRadius(parseFloat(Utils.getParameterByName('edlRadius')));
}
if (Utils.getParameterByName('edlStrength')) {
this.setEDLStrength(parseFloat(Utils.getParameterByName('edlStrength')));
}
if (Utils.getParameterByName('pointBudget')) {
this.setPointBudget(parseFloat(Utils.getParameterByName('pointBudget')));
}
if (Utils.getParameterByName('showBoundingBox')) {
let enabled = Utils.getParameterByName('showBoundingBox') === 'true';
if (enabled) {
this.setShowBoundingBox(true);
} else {
this.setShowBoundingBox(false);
}
}
if (Utils.getParameterByName('material')) {
let material = Utils.getParameterByName('material');
this.setMaterial(material);
}
if (Utils.getParameterByName('pointSizing')) {
let sizing = Utils.getParameterByName('pointSizing');
this.setPointSizing(sizing);
}
if (Utils.getParameterByName('quality')) {
let quality = Utils.getParameterByName('quality');
this.setQuality(quality);
}
if (Utils.getParameterByName('position')) {
let value = Utils.getParameterByName('position');
value = value.replace('[', '').replace(']', '');
let tokens = value.split(';');
let x = parseFloat(tokens[0]);
let y = parseFloat(tokens[1]);
let z = parseFloat(tokens[2]);
this.scene.view.position.set(x, y, z);
}
if (Utils.getParameterByName('target')) {
let value = Utils.getParameterByName('target');
value = value.replace('[', '').replace(']', '');
let tokens = value.split(';');
let x = parseFloat(tokens[0]);
let y = parseFloat(tokens[1]);
let z = parseFloat(tokens[2]);
this.scene.view.lookAt(new THREE.Vector3(x, y, z));
}
if (Utils.getParameterByName('background')) {
let value = Utils.getParameterByName('background');
this.setBackground(value);
}
// if(Utils.getParameterByName("elevationRange")){
// let value = Utils.getParameterByName("elevationRange");
// value = value.replace("[", "").replace("]", "");
// let tokens = value.split(";");
// let x = parseFloat(tokens[0]);
// let y = parseFloat(tokens[1]);
//
// this.setElevationRange(x, y);
// //this.scene.view.target.set(x, y, z);
// }
};
// ------------------------------------------------------------------------------------
// Viewer Internals
// ------------------------------------------------------------------------------------
createControls () {
{ // create FIRST PERSON CONTROLS
this.fpControls = new FirstPersonControls(this, this.mainViewport);
this.fpControls.enabled = false;
this.fpControls.addEventListener('start', this.disableAnnotations.bind(this));
this.fpControls.addEventListener('end', this.enableAnnotations.bind(this));
/* this.addEventListener("loadPointCloudDone", ()=>{
let boundPlane = new THREE.Box3()
boundPlane.expandByPoint(this.bound.boundingBox.min.clone())//最低高度为bound的最低
boundPlane.expandByPoint(this.bound.boundingBox.max.clone().setZ(this.bound.center.z))//最高高度为bound的中心高度
FirstPersonControls.boundPlane = boundPlane
FirstPersonControls.standardSpeed = THREE.Math.clamp( Math.sqrt(this.bound.boundSize.length() )/ 100 , 0.02,0.5); //在这个boundPlane中的速度
}) */
}
// { // create GEO CONTROLS
// this.geoControls = new GeoControls(this.scene.camera, this.renderer.domElement);
// this.geoControls.enabled = false;
// this.geoControls.addEventListener("start", this.disableAnnotations.bind(this));
// this.geoControls.addEventListener("end", this.enableAnnotations.bind(this));
// this.geoControls.addEventListener("move_speed_changed", (event) => {
// this.setMoveSpeed(this.geoControls.moveSpeed);
// });
// }
{ // create ORBIT CONTROLS
this.orbitControls = new OrbitControls(this);
this.orbitControls.enabled = false;
this.orbitControls.addEventListener('start', this.disableAnnotations.bind(this));
this.orbitControls.addEventListener('end', this.enableAnnotations.bind(this));
}
/* { // create EARTH CONTROLS
this.earthControls = new EarthControls(this);
this.earthControls.enabled = false;
this.earthControls.addEventListener('start', this.disableAnnotations.bind(this));
this.earthControls.addEventListener('end', this.enableAnnotations.bind(this));
}
{ // create DEVICE ORIENTATION CONTROLS
this.deviceControls = new DeviceOrientationControls(this);
this.deviceControls.enabled = false;
this.deviceControls.addEventListener('start', this.disableAnnotations.bind(this));
this.deviceControls.addEventListener('end', this.enableAnnotations.bind(this));
} */
/* { // create VR CONTROLS
this.vrControls = new VRControls(this);
this.vrControls.enabled = false;
this.vrControls.addEventListener('start', this.disableAnnotations.bind(this));
this.vrControls.addEventListener('end', this.enableAnnotations.bind(this));
} */
};
toggleSidebar () {
let renderArea = $('#potree_render_area');
let isVisible = renderArea.css('left') !== '0px';
if (isVisible) {
renderArea.css('left', '0px');
} else {
renderArea.css('left', '300px');
}
};
toggleMap () {
// let map = $('#potree_map');
// map.toggle(100);
if (this.mapView) {
this.mapView.toggle();
}
};
onGUILoaded(callback){
if(this.guiLoaded){
callback();
}else{
this.guiLoadTasks.push(callback);
}
}
promiseGuiLoaded(){
return new Promise( resolve => {
if(this.guiLoaded){
resolve();
}else{
this.guiLoadTasks.push(resolve);
}
});
}
loadGUI(callback){
if(callback){
this.onGUILoaded(callback);
}
let viewer = this;
let sidebarContainer = $('#potree_sidebar_container');
sidebarContainer.load(new URL(Potree.scriptPath + '/' + (Potree.settings.sidebar || 'sidebar1.html')).href, () => {
sidebarContainer.css('width', '300px');
sidebarContainer.css('height', '100%');
let imgMenuToggle = document.createElement('img');
imgMenuToggle.src = new URL(Potree.resourcePath + '/icons/menu_button.svg').href;
imgMenuToggle.onclick = this.toggleSidebar;
imgMenuToggle.classList.add('potree_menu_toggle');
let imgMapToggle = document.createElement('img');
imgMapToggle.src = new URL(Potree.resourcePath + '/icons/map_icon.png').href;
imgMapToggle.style.display = 'none';
imgMapToggle.onclick = e => { this.toggleMap(); };
imgMapToggle.id = 'potree_map_toggle';
let elButtons = $("#potree_quick_buttons").get(0);
elButtons.append(imgMenuToggle);
elButtons.append(imgMapToggle);
VRButton.createButton(this.renderer).then(vrButton => {
if(vrButton == null){
console.log("VR not supported or active.");
return;
}
this.renderer.xr.enabled = true;
let element = vrButton.element;
element.style.position = "";
element.style.bottom = "";
element.style.left = "";
element.style.margin = "4px";
element.style.fontSize = "100%";
element.style.width = "2.5em";
element.style.height = "2.5em";
element.style.padding = "0";
element.style.textShadow = "black 2px 2px 2px";
element.style.display = "block";
elButtons.append(element);
vrButton.onStart(() => {
this.dispatchEvent({type: "vr_start"});
});
vrButton.onEnd(() => {
this.dispatchEvent({type: "vr_end"});
});
});
/* this.mapView = new MapView(this);
this.mapView.init(); */
i18n.init({
lng: 'en',
resGetPath: Potree.resourcePath + '/lang/__lng__/__ns__.json',
preload: ['en', 'fr', 'de', 'jp', 'se', 'es', 'zh'],
getAsync: true,
debug: false
}, function (t) {
// Start translation once everything is loaded
$('body').i18n();
});
$(() => {
//initSidebar(this);
let sidebar = new Sidebar(this);
sidebar.init();
this.sidebar = sidebar;
//if (callback) {
// $(callback);
//}
let elProfile = $('').load(new URL(Potree.scriptPath + '/profile.html').href, () => {
$(document.body).append(elProfile.children());
this.profileWindow = new ProfileWindow(this);
this.profileWindowController = new ProfileWindowController(this);
$('#profile_window').draggable({
handle: $('#profile_titlebar'),
containment: $(document.body)
});
$('#profile_window').resizable({
containment: $(document.body),
handles: 'n, e, s, w'
});
$(() => {
this.guiLoaded = true;
for(let task of this.guiLoadTasks){
task();
}
});
});
});
});
return this.promiseGuiLoaded();
}
setLanguage (lang) {
i18n.setLng(lang);
$('body').i18n();
}
setServer (server) {
this.server = server;
}
initDragAndDrop(){
function allowDrag(e) {
e.dataTransfer.dropEffect = 'copy';
e.preventDefault();
}
let dropHandler = async (event) => {
console.log(event);
event.preventDefault();
for(const item of event.dataTransfer.items){
console.log(item);
if(item.kind !== "file"){
continue;
}
const file = item.getAsFile();
const isJson = file.name.toLowerCase().endsWith(".json");
const isGeoPackage = file.name.toLowerCase().endsWith(".gpkg");
if(isJson){
try{
const text = await file.text();
const json = JSON.parse(text);
if(json.type === "Potree"){
Potree.loadProject(viewer, json);
}
}catch(e){
console.error("failed to parse the dropped file as JSON");
console.error(e);
}
}else if(isGeoPackage){
const hasPointcloud = viewer.scene.pointclouds.length > 0;
if(!hasPointcloud){
let msg = "At least one point cloud is needed that specifies the ";
msg += "coordinate reference system before loading vector data.";
console.error(msg);
}else{
proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
proj4.defs("pointcloud", this.getProjection());
let transform = proj4("WGS84", "pointcloud");
const buffer = await file.arrayBuffer();
const params = {
transform: transform,
source: file.name,
};
const geo = await Potree.GeoPackageLoader.loadBuffer(buffer, params);
viewer.scene.addGeopackage(geo);
}
}
}
};
$("body")[0].addEventListener("dragenter", allowDrag);
$("body")[0].addEventListener("dragover", allowDrag);
$("body")[0].addEventListener("drop", dropHandler);
}
updateAnnotations () {
if(!this.visibleAnnotations){
this.visibleAnnotations = new Set();
}
this.scene.annotations.updateBounds();
this.scene.cameraP.updateMatrixWorld();
this.scene.cameraO.updateMatrixWorld();
let distances = [];
let renderAreaSize = this.renderer.getSize(new THREE.Vector2());
let viewer = this;
let visibleNow = [];
this.scene.annotations.traverse(annotation => {
if (annotation === this.scene.annotations) {
return true;
}
if (!annotation.visible) {
return false;
}
annotation.scene = this.scene;
let element = annotation.domElement;
let position = annotation.position.clone();
position.add(annotation.offset);
if (!position) {
position = annotation.boundingBox.getCenter(new THREE.Vector3());
}
let distance = viewer.scene.cameraP.position.distanceTo(position);
let radius = annotation.boundingBox.getBoundingSphere(new THREE.Sphere()).radius;
let screenPos = new THREE.Vector3();
let screenSize = 0;
{
// SCREEN POS
screenPos.copy(position).project(this.scene.getActiveCamera());
screenPos.x = renderAreaSize.x * (screenPos.x + 1) / 2;
screenPos.y = renderAreaSize.y * (1 - (screenPos.y + 1) / 2);
// SCREEN SIZE
if(viewer.scene.cameraMode == CameraMode.PERSPECTIVE) {
let fov = Math.PI * viewer.scene.cameraP.fov / 180;
let slope = Math.tan(fov / 2.0);
let projFactor = 0.5 * renderAreaSize.y / (slope * distance);
screenSize = radius * projFactor;
} else {
screenSize = Utils.projectedRadiusOrtho(radius, viewer.scene.cameraO.projectionMatrix, renderAreaSize.x, renderAreaSize.y);
}
}
element.css("left", screenPos.x + "px");
element.css("top", screenPos.y + "px");
//element.css("display", "block");
let zIndex = 10000000 - distance * (10000000 / this.scene.cameraP.far);
if(annotation.descriptionVisible){
zIndex += 10000000;
}
element.css("z-index", parseInt(zIndex));
if(annotation.children.length > 0){
let expand = screenSize > annotation.collapseThreshold || annotation.boundingBox.containsPoint(this.scene.getActiveCamera().position);
annotation.expand = expand;
if (!expand) {
//annotation.display = (screenPos.z >= -1 && screenPos.z <= 1);
let inFrustum = (screenPos.z >= -1 && screenPos.z <= 1);
if(inFrustum){
visibleNow.push(annotation);
}
}
return expand;
} else {
//annotation.display = (screenPos.z >= -1 && screenPos.z <= 1);
let inFrustum = (screenPos.z >= -1 && screenPos.z <= 1);
if(inFrustum){
visibleNow.push(annotation);
}
}
});
let notVisibleAnymore = new Set(this.visibleAnnotations);
for(let annotation of visibleNow){
annotation.display = true;
notVisibleAnymore.delete(annotation);
}
this.visibleAnnotations = visibleNow;
for(let annotation of notVisibleAnymore){
annotation.display = false;
}
}
updateMaterialDefaults(pointcloud){
// PROBLEM STATEMENT:
// * [min, max] of intensity, source id, etc. are computed as point clouds are loaded
// * the point cloud material won't know the range it should use until some data is loaded
// * users can modify the range at runtime, but sensible default ranges should be
// applied even if no GUI is present
// * display ranges shouldn't suddenly change even if the actual range changes over time.
// e.g. the root node has intensity range [1, 478]. One of the descendants increases range to
// [0, 2047]. We should not automatically change to the new range because that would result
// in sudden and drastic changes of brightness. We should adjust the min/max of the sidebar slider.
const material = pointcloud.material;
const attIntensity = pointcloud.getAttribute("intensity");
if(attIntensity != null && material.intensityRange[0] === Infinity){
material.intensityRange = [...attIntensity.range];
}
// const attIntensity = pointcloud.getAttribute("intensity");
// if(attIntensity && material.intensityRange[0] === Infinity){
// material.intensityRange = [...attIntensity.range];
// }
// let attributes = pointcloud.getAttributes();
// for(let attribute of attributes.attributes){
// if(attribute.range){
// let range = [...attribute.range];
// material.computedRange.set(attribute.name, range);
// //material.setRange(attribute.name, range);
// }
// }
}
update(delta, timestamp){
viewer.addTimeMark('update','start')
TWEEN.update(timestamp);
transitions.update(delta);//写在开头,因为这时候最为固定,计时准确
this.dispatchEvent({
type: 'update_start',
delta: delta,
timestamp: timestamp});
this.updateScreenSize() //判断是否改变canvas大小
const scene = this.scene;
const camera = scene.getActiveCamera();
const visiblePointClouds = this.scene.pointclouds.filter(pc => pc.visible)
Potree.pointLoadLimit = Potree.pointBudget * 2;
/* const lTarget = camera.position.clone().add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1000));
this.scene.directionalLight.position.copy(camera.position);
this.scene.directionalLight.lookAt(lTarget); */
for (let pointcloud of visiblePointClouds) {
pointcloud.showBoundingBox = this.showBoundingBox;
pointcloud.generateDEM = this.generateDEM;
pointcloud.minimumNodePixelSize = this.minNodeSize;
let material = pointcloud.material;
material.uniforms.uFilterReturnNumberRange.value = this.filterReturnNumberRange;
material.uniforms.uFilterNumberOfReturnsRange.value = this.filterNumberOfReturnsRange;
material.uniforms.uFilterGPSTimeClipRange.value = this.filterGPSTimeRange;
material.uniforms.uFilterPointSourceIDClipRange.value = this.filterPointSourceIDRange;
material.classification = this.classifications;
material.recomputeClassification();
this.updateMaterialDefaults(pointcloud);
}
{
if(this.showBoundingBox){
let bbRoot = this.scene.scene.getObjectByName("potree_bounding_box_root");
if(!bbRoot){
let node = new THREE.Object3D();
node.name = "potree_bounding_box_root";
this.scene.scene.add(node);
bbRoot = node;
}
let visibleBoxes = [];
for(let pointcloud of this.scene.pointclouds){
for(let node of pointcloud.visibleNodes.filter(vn => vn.boundingBoxNode !== undefined)){
let box = node.boundingBoxNode;
visibleBoxes.push(box);
}
}
bbRoot.children = visibleBoxes;
}
}
if(this.boundNeedUpdate)this.updateModelBound() //add
this.scene.cameraP.fov = this.fov;
let controls = this.getControls();
if (controls === this.deviceControls) {
this.controls.setScene(scene);
this.controls.update(delta);
this.scene.cameraP.position.copy(scene.view.position);
this.scene.cameraO.position.copy(scene.view.position);
} else if (controls !== null) {
controls.setScene(scene);
controls.update(delta);
//更新camera
this.viewports.forEach(viewport=>{
if(!viewport.active)return
viewport.view.applyToCamera(viewport.camera)
})
}
this.lastFrameChanged = this.cameraChanged()//判断camera画面是否改变
{ // update clip boxes
let boxes = [];
// volumes with clipping enabled
boxes.push(...this.scene.volumes.filter(v => (v.clip && v instanceof BoxVolume)));
// profile segments
for(let profile of this.scene.profiles){
boxes.push(...profile.boxes);
}
// Needed for .getInverse(), pre-empt a determinant of 0, see #815 / #816
let degenerate = (box) => box.matrixWorld.determinant() !== 0 && box.clip;
let clipBoxes = boxes.filter(degenerate).map( box => {
box.updateMatrixWorld();
let boxInverse = box.matrixWorld.clone().invert();
//let boxPosition = box.getWorldPosition(new THREE.Vector3());
return {box: box, inverse: boxInverse/* , position: boxPosition */};
});
//改
let bigClipInBox = clipBoxes.find(e=>e.box.clipTask == ClipTask.SHOW_INSIDE_Big && !e.box.highlight)//裁剪下载 when this.modules.Clip.editing
let clipBoxes_in = clipBoxes.filter(e=>e.box.clipTask == ClipTask.SHOW_INSIDE && !e.box.highlight)
let clipBoxes_out = clipBoxes.filter(e=>e.box.clipTask == ClipTask.SHOW_OUTSIDE && !e.box.highlight)
let highlightBoxes = clipBoxes.filter(e=>e.box.highlight )
//let prismPolygons = this.modules.volumeComputer && this.modules.volumeComputer.entered ? viewer.scene.measurements.filter(e=>(e.measureType == 'MulDistance Ring') && !e.isNew) : []
let prismPolygons = this.modules.volumeComputer ? this.modules.volumeComputer.prisms.filter(e=>!e.isNew && e.visible && !e.dontHighlight) : []
// set clip volumes in material
for(let pointcloud of visiblePointClouds){
pointcloud.material.setClipBoxes(bigClipInBox, clipBoxes_in, clipBoxes_out, highlightBoxes, prismPolygons);
}
}
{
for(let pointcloud of visiblePointClouds){
pointcloud.material.elevationGradientRepeat = this.elevationGradientRepeat;
}
}
{ // update navigation cube
this.navigationCube.update(camera.rotation);
}
this.updateAnnotations();
if(this.mapView){
this.mapView.update(delta);
if(this.mapView.sceneProjection){
$( "#potree_map_toggle" ).css("display", "block");
}
}
this.transformationTool.update();
if(Potree.settings.editType != 'pano' && Potree.settings.editType != 'merge'){
this.modules.ParticleEditor.update(delta)
this.mapViewer.update(delta) //地图更新
}
this.dispatchEvent({ type: 'update', delta: delta, timestamp: timestamp}); //在有sidebar时耗高cpu,占本update函数80%
viewer.addTimeMark('update','end')
//add ------
this.reticule.updateVisible()
}
updateViewPointcloud(camera, areaSize, isViewport){
let result = Potree.updatePointClouds(this.scene.pointclouds, camera, areaSize );
//if(isViewport)return
const tStart = performance.now();
const campos = camera.position;
let closestImage = Infinity;
for(const images of this.scene.orientedImages){
for(const image of images.images){
const distance = image.mesh.position.distanceTo(campos);
closestImage = Math.min(closestImage, distance);
}
}
//const tEnd = performance.now();
//改:不根据点云修改视野near far
var near = camera.near, far = camera.far
if(!camera.limitFar && result.lowestSpacing !== Infinity){
//let near = result.lowestSpacing * 10.0;
let far = -this.getBoundingBox().applyMatrix4(camera.matrixWorldInverse).min.z;
far = Math.max(far * 1.5, 10000);
//near = Math.min(100.0, Math.max(0.01, near));
//near = Math.min(near, closestImage);
far = Math.max(far, near + 10000);
/* if(near === Infinity){
near = 0.1;
} */
//camera.near = near; //为了其他物体的显示,不修改near
camera.far = far;
}
/* if(this.scene.cameraMode == CameraMode.ORTHOGRAPHIC) {//???
camera.near = -camera.far;
} */
if(/* near != camera.near || */far != camera.far){
camera.updateProjectionMatrix()
}
//注:pointcloud.visibleNodes会随着near far自动更新
}
getPRenderer(){
if(this.useHQ){
if (!this.hqRenderer) {
this.hqRenderer = new HQSplatRenderer(this);
}
this.hqRenderer.useEDL = this.useEDL;
return this.hqRenderer;
}else{
/* if (this.useEDL && Features.SHADER_EDL.isSupported()) {
if (!this.edlRenderer) {
this.edlRenderer = new EDLRenderer(this);
}
return this.edlRenderer;
} else {
if (!this.potreeRenderer) {
this.potreeRenderer = new PotreeRenderer(this);
}
return this.potreeRenderer;
} */
if (!this.edlRenderer) {
this.edlRenderer = new EDLRenderer(this);
}
return this.edlRenderer;
}
}
renderVR(){//渲染部分没改完
let renderer = this.renderer;
renderer.setClearColor(0x550000, 0);
renderer.clear();
let xr = renderer.xr;
let dbg = new THREE.PerspectiveCamera();
let xrCameras = xr.getCamera(dbg);
if(xrCameras.cameras.length !== 2){
return;
}
let makeCam = this.vrControls.getCamera.bind(this.vrControls);
{ // clear framebuffer
if(viewer.background === "skybox"){
renderer.setClearColor(0xff0000, 1);
}else if(viewer.background === "gradient"){
renderer.setClearColor(0x112233, 1);
}else if(viewer.background === "black"){
renderer.setClearColor(0x000000, 1);
}else if(viewer.background === "white"){
renderer.setClearColor(0xFFFFFF, 1);
}else{
renderer.setClearColor(0x000000, 0);
}
renderer.clear();
}
// render background
if(this.background === "skybox"){
let {skybox} = this;
let cam = makeCam();
skybox.camera.rotation.copy(cam.rotation);
skybox.camera.fov = cam.fov;
skybox.camera.aspect = cam.aspect;
// let dbg = new THREE.Object3D();
let dbg = skybox.parent;
// dbg.up.set(0, 0, 1);
dbg.rotation.x = Math.PI / 2;
// skybox.camera.parent = dbg;
// dbg.children.push(skybox.camera);
dbg.updateMatrix();
dbg.updateMatrixWorld();
skybox.camera.updateMatrix();
skybox.camera.updateMatrixWorld();
skybox.camera.updateProjectionMatrix();
renderer.render(skybox.scene, skybox.camera);
// renderer.render(skybox.scene, cam);
}else if(this.background === "gradient"){
// renderer.render(this.scene.sceneBG, this.scene.cameraBG);
}
this.renderer.xr.getSession().updateRenderState({
depthNear: 0.1,
depthFar: 10000
});
let cam = null;
let view = null;
{ // render world scene
cam = makeCam();
cam.position.z -= 0.8 * cam.scale.x;
cam.parent = null;
// cam.near = 0.05;
cam.near = viewer.scene.getActiveCamera().near;
cam.far = viewer.scene.getActiveCamera().far;
cam.updateMatrix();
cam.updateMatrixWorld();
this.scene.scene.updateMatrix();
this.scene.scene.updateMatrixWorld();
this.scene.scene.matrixAutoUpdate = false;
let camWorld = cam.matrixWorld.clone();
view = camWorld.clone().invert();
this.scene.scene.matrix.copy(view);
this.scene.scene.matrixWorld.copy(view);
cam.matrix.identity();
cam.matrixWorld.identity();
cam.matrixWorldInverse.identity();
renderer.render(this.scene.scene, cam);
this.scene.scene.matrixWorld.identity();
}
for(let pointcloud of this.scene.pointclouds){
let viewport = xrCameras.cameras[0].viewport;
pointcloud.material.useEDL = false;
pointcloud.screenHeight = viewport.height;
pointcloud.screenWidth = viewport.width;
// automatically switch to paraboloids because they cause far less flickering in VR,
// when point sizes are larger than around 2 pixels
// if(Features.SHADER_INTERPOLATION.isSupported()){
// pointcloud.material.shape = Potree.PointShape.PARABOLOID;
// }
}
// render point clouds
for(let xrCamera of xrCameras.cameras){
let v = xrCamera.viewport;
renderer.setViewport(v.x, v.y, v.width, v.height);
// xrCamera.fov = 90;
{ // estimate VR fov
let proj = xrCamera.projectionMatrix;
let inv = proj.clone().invert();
let p1 = new THREE.Vector4(0, 1, -1, 1).applyMatrix4(inv);
let rad = p1.y
let fov = 180 * (rad / Math.PI);
xrCamera.fov = fov;
}
for(let pointcloud of this.scene.pointclouds){
const {material} = pointcloud;
material.useEDL = false;
}
let vrWorld = view.clone().invert();
vrWorld.multiply(xrCamera.matrixWorld);
let vrView = vrWorld.clone().invert();
this.pRenderer.render(this.scene.scenePointCloud, xrCamera, null, {
viewOverride: vrView,
});
}
{ // render VR scene
let cam = makeCam();
cam.parent = null;
renderer.render(this.sceneVR, cam);
}
renderer.resetState();
}
clear(params={}){
let background = params.background || this.background;
let backgroundOpacity = params.backgroundOpacity == void 0 ? this.backgroundOpacity : params.backgroundOpacity//如果想完全透明,只需要backgroundOpacity为0
let renderer = this.renderer
//let gl = renderer.getContext()
if(background instanceof THREE.Color){ //add
renderer.setClearColor(background, backgroundOpacity);
}else if(background === "skybox"){
renderer.setClearColor(0x000000, backgroundOpacity);
} else if (background === 'gradient') {
renderer.setClearColor(0x000000, backgroundOpacity);
} else if (background === 'black') {
renderer.setClearColor(0x000000, 1);
} else if (background === 'white') {
renderer.setClearColor(0xFFFFFF, 1);
} else {
renderer.setClearColor(background, backgroundOpacity);
}
params.target || renderer.clear();
}
getBuffer(viewport){//根据不同viewport返回rtEDL的texture
var buffer = this.buffers.get(viewport)
if(!buffer){
buffer = new THREE.WebGLRenderTarget(viewport.resolution.x, viewport.resolution.y, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
type: THREE.FloatType
});
this.buffers.set(viewport, rtEDL)
}
return buffer
}
async renderDefault(params_={}){
if(!this.visible ) return
let pRenderer = this.getPRenderer();
let viewports = params_.viewports || this.viewports
viewer.addTimeMark('renderDefault','start')
//console.log('render', viewports.map(e=>e.name).join(','))
let renderSize
if(params_.target){
renderSize = new THREE.Vector2(params_.target.width, params_.target.height) //是画布大小
//可能需要viewer.setSize
}else{
renderSize = this.renderer.getSize(new THREE.Vector2()); //是client大小
}
///let needsResize = viewports.length > 1 || params_.resize //去掉原因:因为不需要渲染的viewport不在此中所以无法判断几个viewport
for(let i=0; i{
if(object.material){
Potree.Utils.updateVisible(object, 'renderOpa',
(params.renderBeforeCloud && (object.material.opacity<1 || !object.material.depthTest) || (!params.renderBeforeCloud) && (object.material.opacity==1 && object.material.depthTest))? false:true)
//点云之前渲染的话隐藏半透明的, 点云之后渲染的话隐藏不透明的。 depthTest==false的也最后渲染
}
})//ground的材质中opacity为1,所以被当做不透明了
}
viewer.dispatchEvent({type: "render.begin2" , name:'scene', viewport:params.viewport })
renderer.render(this.scene.scene, camera);
if('renderBeforeCloud' in params){
this.scene.scene.traverse((object)=>{
if(object.material){
Potree.Utils.updateVisible(object, 'renderOpa', true) //恢复
}
})
}
}
this.dispatchEvent({type: "render.pass.scene", viewer: viewer});
}
renderOverlay2(params){
let renderer = params.renderer || this.renderer
let camera = params.camera ? params.camera : this.scene.getActiveCamera();
//清除深度 !!!!
renderer.clearDepth();
if(!params.magnifier){
//测量线
this.dispatchEvent({type: "render.pass.perspective_overlay", camera, screenshot:params.screenshot,viewport:params.viewport, renderer});
if(!params.screenshot && params.viewport.name != "mapViewport" ){
Potree.Utils.setCameraLayers(camera, ['magnifier']) //magnifier 遮住测量线
renderer.render(this.scene.scene, camera);
}
}
if(params.viewport.name != "mapViewport" ) {
Potree.Utils.setCameraLayers(camera, ['volume','transformationTool'])
//viewer.dispatchEvent({type: "render.begin2" , name:'clip&trans',viewport:params.viewport })
renderer.render(this.clippingTool.sceneVolume, camera); //official 可以删
renderer.render(this.transformationTool.scene, camera);
}
}
setLimitFar(state){//切换是否limitFar
viewer.mainViewport.camera.limitFar = !!state
if(state){
viewer.mainViewport.camera.near = 0.02;
viewer.mainViewport.camera.far = Potree.settings.displayMode == 'showPanos' ? viewer.farWhenShowPano : Potree.settings.cameraFar;
viewer.mainViewport.camera.updateProjectionMatrix()
}
}
setClipState(state){//有时候需要暂时关闭下clip
state = !!state
if(this.clipUnabled == !state)return
this.scene.volumes.filter(v=>/* v.clip && */ v instanceof Potree.BoxVolume).map(volume=>{
volume.clip = state
Potree.Utils.updateVisible(volume, 'setClipState', state)
})
this.clipUnabled = !state
}
/* 大规模WebGL应用引发浏览器崩溃的几种情况及解决办法
https://blog.csdn.net/weixin_30378311/article/details/94846947 */
async render(params={}){//add params
viewer.addTimeMark('render','start')
const vrActive = this.renderer.xr.isPresenting;
let SiteModel = viewer.modules.SiteModel
if(this.screenshoting && !params.screenshot)return //正在截图
let s = SiteModel.editing && SiteModel.selected && (SiteModel.selected.buildType == 'room' || SiteModel.selected.buildType == 'floor') //空间模型的房间选中材质是需要depth的,这时候需要绘制两次点云
Potree.settings.pointEnableRT = !this.screenshoting && (this.scene.measurements.filter(e=>e.visible).length > 0 || s )
if(vrActive){
this.renderVR();
}else{
let specialRender = !!params.viewports
let viewports = params.viewports || this.viewports
if(!this.needRender){
viewports = viewports.filter(v=>v.needRender) //可以渲染的条件是viewer或viewport的needRender为true
}
viewports = viewports.filter(v=>v.active)
if(viewports.length > 0){
params.viewports = viewports
if(this.outlinePass.selectedObjects.length && this.outlinePass.edgeStrength > 0 && !params.screenshot){
let scenes = this.inputHandler.interactiveScenes.concat(this.scene.scene).concat(viewer.scene.scenePointCloud)
this.composer.render(scenes, null, this.viewports, this.renderDefault.bind(this));
}else{
await this.renderDefault(params);
}
}
if(!specialRender) this.needRender = false
}
viewer.addTimeMark('render','end')
}
startScreenshot(info={}, width=800, height=400, compressRatio ){//add
//let deferred = info.deferred || $.Deferred();
let getImageDeferred = info.getImageDeferred || $.Deferred();
let finishDeferred = info.finishDeferred || $.Deferred();
let viewerMaster = info.map ? this.mapViewer : this; //截图主体
let useMap = info.map || info.type == 'measure' || info.type.includes('prism2d')
if(Potree.settings.displayMode == 'showPanos' && viewer.mainViewport.view.isFlying('pos')){//如果在飞,飞完再截图
info.getImageDeferred = getImageDeferred , info.finishDeferred = finishDeferred
let f = ()=>{
this.startScreenshot(info, width, height, compressRatio)
}
viewer.mainViewport.view.addEventListener('flyingDone', f, {once:true})
return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()}
}
var startTime = Date.now()
//抗锯齿待加 1 post处理 2截图大张再抗锯齿缩小
this.pauseTestMaxLevel = true
this.screenshoting = true
console.log('startScreenshot: '+startTime)
let updateCamera = ()=>{
viewports.forEach(e=>{
e.view.applyToCamera(e.camera) //因为fly时只更新了view所以要强制更新下camera
this.dispatchEvent({ //update map and sprite, mapChanged
type: "camera_changed",
camera: e.camera,
viewport : e,
changeInfo:{positionChanged:true,changed:true}
})
})
}
let screenshot = async ()=>{
let pose
useMap && (viewer.mapViewer.needRender = true)
info.beforeScreenshot && info.beforeScreenshot()
let render = async ()=>{
this.needRender = true
viewports.forEach(e=>e.active = true)
if(info.useRenderTarget){
//离屏渲染 有抗锯齿问题、在手机上速度慢 主视图一样会变内容,还是不用这个了
var { dataUrl } = viewerMaster.makeScreenshot( new THREE.Vector2(width,height), null, compressRatio );
}else{
//直接渲染 会改变canvas大小
let canvas = viewerMaster.renderer.domElement
//canvas.width = width, canvas.height = height //不需要改canvas大小, 只需要 this.renderer.setSize(width, height ); 前面updateScreenSize已经执行
await viewerMaster.render({ screenshot : true, width , height, resize :true, needWaitLoadPoint:!!info.splitRenderInfo, maxTimeForPointLoad:info.maxTimeForPointLoad }); //需要resize
/* if(info.type.includes('prism2d')){//o.map要为true
viewer.dispatchEvent({type:'resize', viewport:viewer.mapViewer.viewports[0]}) //借用viewer通知lineMaterial resize
viewerMaster.renderOverlay()
} */
var dataUrl = canvas.toDataURL('image/png',compressRatio)
}
return dataUrl
}
let dataUrl
if(info.splitRenderInfo){//为了防崩溃,分区域批次渲染
let {wc , hc} = info.splitRenderInfo
let camera = viewer.mainViewport.camera
let dataUrls = []
let width_ = Math.ceil(1/wc * width) , height_ = Math.ceil(1/hc * height) //可能加起来比原图大,没事到时候边界会重合
viewer.updateScreenSize({forceUpdateSize:true, width:width_, height:height_, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
for(let i=0;i{
oldStates.viewports.forEach(old=>{//恢复相机
var viewport = viewports.find(v=>v.name == old.name);
viewport.left = old.left; viewport.bottom = old.bottom;
viewport.width = old.width; viewport.height = old.height
viewport.view.copy(old.view)
//viewport.view.applyToCamera(viewport.camera);
if(viewport.camera.isOrthographicCamera){
viewport.camera.zoom = viewport.view.zoom
}
})
viewerMaster.updateScreenSize({forceUpdateSize:true})//更新像素
/* oldStates.viewports.forEach(old=>{//恢复相机
var viewport = [mapViewport, mainViewport].find(v=>v.name == old.name);
this.dispatchEvent({ //update map
type: "camera_changed",
camera: viewport.camera,
viewport : viewport
})
}) */
updateCamera()
finishDeferred.resolve({dataUrl, pose})
info.map || setTimeout(()=>{
if(!this.screenshoting){
//Potree.settings.pointNoLimit = false
Potree.settings.pointDensity = 'high'
//console.warn('恢复pointDensity')
/* if(viewer.scene.pointclouds[0].material.oldSize_ ){
viewer.scene.pointclouds[0].material.size = viewer.scene.pointclouds[0].material.oldSize_
viewer.scene.pointclouds[0].material.oldSize_ = null
} */
}
},500) //延迟:避免连续多次截图时释放点云
this.screenshoting = false
console.log('screenshot done: ' + startTime, 'costTime', (Date.now() - startTime) + 'ms')
}
{//恢复:
this.backgroundOpacity = oldStates.bgOpacity;
this.background = oldStates.background;
this.pauseTestMaxLevel = false
oldStates.displayMode && (Potree.settings.displayMode = oldStates.displayMode)
if(info.type == 'measure' || info.type.includes('prism2d')){
this.modules.SiteModel.pauseUpdateEntity = false
this.focusDatasets = null
this.scene.measurements.forEach(e=>Potree.Utils.updateVisible(e, 'screenshot',true))
info.type == 'measure' && info.measurement.setSelected(false, 'screenshot')
}
if(info.type.includes('prism2d')){
//viewer.mapViewer.transparentBG = false;
(info.prisms || [info.prism]).forEach(prism=>{
prism.changeStyleForScreenshot(false)
if(info.type == 'prism2d-single'){
prism.baseModel && (Potree.Utils.updateVisible(prism.baseModel, 'screenshot', false, 3, 'cancel'), Potree.Utils.setObjectLayers(prism.baseModel, 'model'))
prism.setEditState(false)
}
})
}
this.images360.panos.forEach(pano=>{
Potree.Utils.updateVisible(pano, 'screenshot', true)
})
Potree.Utils.updateVisible(this.reticule, 'screenshot', true)
if(useMap){
Potree.Utils.updateVisible(this.mapViewer.cursor, 'screenshot', true)
if(oldStates.attachedToViewer != this.mapViewer.attachedToViewer){
this.mapViewer.attachToMainViewer(!!oldStates.attachedToViewer )
}
if(oldStates.attachedToViewer && this.mapViewer.splitDir != oldStates.oldSplitDir){
this.mapViewer.changeSplitScreenDir(oldStates.oldSplitDir)
}
mapViewport.noPointcloud = oldStates.noPointcloud
Potree.Utils.updateVisible(viewer.mapViewer.mapLayer.sceneGroup, 'screenshot-prism', true)
}
let recover = ()=>{
if(Potree.settings.displayMode == 'showPanos') {
viewer.images360.flyToPano({pano:oldStates.pano, duration:0, callback:()=>{
finish()
}})
}else{
finish()
}
}
if(info.ifGetPose){
Potree.sdk.scene.getPose().done(pose_ =>{
pose = pose_
getImageDeferred.resolve({dataUrl, pose})
recover()
})
}else{
recover()
}
}
}// screenshot end
let mapViewport
let mainViewport = this.mainViewport
let viewports = [], oldStates = {
viewports:[],
pano: Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : null,
bgOpacity : this.backgroundOpacity,
background: this.background
}
if(!info.type.includes('prism2d')){
viewports.push(mainViewport)
mainViewport.camera.isOrthographicCamera && (mainViewport.view.zoom = mainViewport.camera.zoom)
oldStates.viewports.push(mainViewport.clone())
}
if(useMap){
mapViewport = this.mapViewer.viewports[0]
mapViewport.view.zoom = mapViewport.camera.zoom
viewports.push(mapViewport)
oldStates.viewports.push(mapViewport.clone())
oldStates.attachedToViewer = this.mapViewer.attachedToViewer
oldStates.noPointcloud = mapViewport.noPointcloud
oldStates.displayMode = Potree.settings.displayMode
Potree.Utils.updateVisible(this.mapViewer.cursor, 'screenshot', false)//令mapCursor不可见
}
viewports.forEach(e=>e.active = false) //暂停渲染
if(info.hideMarkers){
this.images360.panos.forEach(pano=>{//令漫游点不可见
Potree.Utils.updateVisible(pano, 'screenshot', false)
})
}
Potree.Utils.updateVisible(this.reticule, 'screenshot', false)//令reticule不可见
if(info.bgOpacity != void 0){
this.backgroundOpacity = info.bgOpacity
}
if(info.background != void 0){
this.background = info.background
}
Potree.settings.pointDensity = info.pointDensity || 'screenshot'
let focusDatasets = (measurements)=>{
this.focusDatasets = []
measurements.forEach(measure=>{
measure.points_datasets.forEach(e=>{
let p = viewer.scene.pointclouds.find(a=>a.dataset_id == e)
if(p && !this.focusDatasets.includes(p)){
this.focusDatasets.push(p)
}
})
})
this.updateFpVisiDatasets()
}
if(info.type == 'measure'){//要截图双屏
this.scene.measurements.forEach(e=>Potree.Utils.updateVisible(e,'screenshot',e == info.measurement) )
info.measurement.setSelected(true, 'screenshot')
//因为分屏后位置才最终确定,才能确定是否显示出floorplan所以先分屏
if(Potree.settings.floorplanEnable){
oldStates.oldSplitDir = this.mapViewer.splitDir
this.mapViewer.attachToMainViewer(true, 'measure', 0.5 , {dir:'leftRight'} )
}
viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
let begin = ()=>{
useMap = this.mapViewer.attachedToViewer
updateCamera()
if(useMap){
let waitMap = ()=>{
console.log('waitMap: '+startTime, Date.now(), this.mapViewer.mapLayer.loadingInProgress )
this.mapViewer.waitLoadDone(screenshot.bind(this))//等待地图所有加载完
}
this.waitPointLoad(waitMap,info.maxTimeForPointLoad)
}else{
this.waitPointLoad(screenshot,info.maxTimeForPointLoad)
}
}
let {promise} = this.focusOnObject(info.measurement, 'measure', 0, {
basePanoSize:1024, gotoBestView:true,
//minMapWidth: THREE.Math.clamp(Math.min(viewer.bound.boundSize.x, viewer.bound.boundSize.y) / (20/* this.mapViewer.mapLayer.maps.find(e=>e.name == 'map').disabled ? 6 : 3*/), 2, 25) //有地图的话为了显示出名字需要更大范围
})//注意:不同角度截图 得到三维的会不一样,因为focusOnObject是根据方向的
promise.done(()=>{
//console.log('promise.done')
//根据当前位置更新floorplan显示
//console.log('view Pos ', this.mainViewport.view.position.toArray())
this.updateDatasetAt(true)
this.modules.SiteModel.updateEntityAt(true, {measure:info.measurement}) //更新测量线所在楼层,展示俯视图
if(!this.modules.SiteModel.inEntity){
focusDatasets([info.measurement])
}
this.modules.SiteModel.pauseUpdateEntity = true
//console.log('currentFloor', this.modules.SiteModel.currentFloor, 'currentDataset', this.atDatasets )
let floorplanShowed = this.mapViewer.mapLayer.maps.some(e => e.name.includes('floorplan') && e.objectGroup.visible)//如果没有floorplan展示就不分屏(如果focus时飞出数据集从而不展示怎么办。尤其是俯瞰比较长的线时容易这样,或许要根据其所在数据集强制显示)
if(!floorplanShowed && this.mapViewer.attachedToViewer){
this.mapViewer.attachToMainViewer(false) //取消分屏
viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视
let {promise} = this.focusOnObject(info.measurement, 'measure', 0, {basePanoSize:1024,gotoBestView:true,} )//因画面比例更改,重新focus
promise.done(()=>{
begin()
})
}else{
begin()
}
})
}else if(info.type.includes('prism2d')){
let points = [], prisms = info.type == 'prism2d-all' ? info.prisms: [info.prism]
this.mapViewer.attachToMainViewer(true, 'screenshot', 1 , {/* dir:'leftRight' */} )
viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
prisms.forEach(prism=>{
points.push(...prism.points)
prism.setSelected(false)
prism.changeStyleForScreenshot(true, {hideLabel: info.type != 'prism2d-all', showName: info.type == 'prism2d-all' })
if(info.type == 'prism2d-single') {
prism.baseModel && (Potree.Utils.updateVisible(prism.baseModel, 'screenshot', true, 3, 'add'), Potree.Utils.setObjectLayers(prism.baseModel, 'bothMapAndScene'))
prism.setEditState(true)
}
})
this.scene.measurements.forEach(e=>Potree.Utils.updateVisible(e,'screenshot', prisms.includes(e)) )
this.backgroundOpacity = 0
let {promise} = this.focusOnObject({points}, 'prisms2d', 0, {
minMapWidth: 0.5 , onlyMap:true, focusBoundCenter:true, boundExpandRatio:1.4
})
promise.done(()=>{
updateCamera()
if(info.type == 'prism2d-all'){//全部 带平面图
focusDatasets(prisms) //更新平面图 可能有漏洞,当不在任何一个数据集内的话?
this.mapViewer.waitLoadDone(screenshot.bind(this))//等待地图所有加载完
}else{//single
mapViewport.noPointcloud = false //显示点云,而非平面图
Potree.settings.displayMode = 'showPointCloud'
Potree.Utils.updateVisible(viewer.mapViewer.mapLayer.sceneGroup, 'screenshot-prism', false)
this.waitPointLoad(screenshot, 5000)
}
})
}else{//default
let done = ()=>{
updateCamera()
if(info.splitRenderInfo){
screenshot()
}else{
this.waitPointLoad(screenshot, info.maxTimeForPointLoad)
}
}
if(info.focusObjectInfo){
viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget}) //更新viewports相机透视 使focusOnObject在此窗口大小下
let {promise} = this.focusOnObject( ...info.focusObjectInfo)
promise.done(()=>{
done()
})
}else{
done()
}
}
/*
测量线的截图因为要调用分屏的,会改变画面
但是普通截图的话,不会改变画面
*/
return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()}
}
async waitPointLoad(done, maxTimeForPointLoad, viewport = viewer.mainViewport, resolution ){
return new Promise((resolve,reject)=>{
let finish
let dealDone = ()=>{
viewer.removeEventListener('update',update)
viewer.removeEventListener('overPointBudget',decreaseLevel)
finish || done && done()
finish = true
resolve()
}
let decreaseLevel = (e)=>{ //降点云level 但基本降完也不会再加载低等级的点云了,所以是否直接不降?即使质量不平均
if(e.restQueueSize < 10)return ;//差不多完成了
let pointclouds = viewer.scene.pointclouds.filter(e=>e.visibleNodes.length)
console.log('准备decreaseLevel, numVisiblePoints', e.numVisiblePoints, '最小numVisiblePoints占比', 1/pointclouds.length )
pointclouds.forEach(pointcloud=>{
let percent = pointcloud.numVisiblePoints / e.numVisiblePoints
//console.log('numVisiblePoints占总比', pointcloud.dataset_id, percent )
if(percent < 1/pointclouds.length)return
let old = pointcloud.maxLevel
let levels = pointcloud.visibleNodes.map(e=>e.getLevel())
let actMaxLevel = Math.max.apply(null, levels) //实际加载到的最高的node level
pointcloud.maxLevel = actMaxLevel - 1
console.warn(pointcloud.dataset_id,'decreaseLevel,新maxLevel', actMaxLevel - 1, '原maxlevel', old )
})
}
/* if(Potree.settings.displayMode == 'showPointCloud'){
viewer.updateScreenSize({forceUpdateSize:true, width, height, forTarget:info.useRenderTarget})//需要先setSize才能加载范围内的点云
} */
//updateCamera()
//viewer.addEventListener('overPointBudget',decreaseLevel)
let update = ()=>{ //前提:已经禁止渲染,仅加载点云
let camera = viewport.forViewOffset ? viewport.forViewOffset.camera : viewport.camera
Potree.updatePointClouds(viewer.scene.pointclouds, camera , resolution || viewport.resolution );
}
viewer.addEventListener('update',update)
update()
let maxTime = maxTimeForPointLoad || 2000
document.hidden && (maxTime *= 6) //离开页面后会变成1秒1帧
setTimeout(()=>{
if(Potree.pointsLoading && Potree.settings.displayMode == 'showPointCloud'){//如果还在加载
viewer.addEventListener('pointsLoaded',()=>{ //点云加载完时(不一定准确)
if(!finish)console.log('加载完毕', ' numVisiblePoints', Potree.numVisiblePoints)
dealDone()
},{once:true})
let overTime = ()=>{//超时不候(其实之后等待地图还会再加载几秒)
if(document.hidden){
return setTimeout(overTime, maxTime)
}
if(!finish)console.warn('超时, numVisiblePoints', Potree.numVisiblePoints)
dealDone()
}
setTimeout(overTime, maxTime)
}else{
console.log('已经加载完,无需再加载点云 numVisiblePoints', Potree.numVisiblePoints)
dealDone()
}
},200)//先加载一段时间
})
}
focusOnObject(object, type, duration, o={} ) {
//飞向热点、测量线等 。
//console.log('focusOnObject: ', object, type)
let deferred = o.deferred || $.Deferred();
let target = new THREE.Vector3, //相机focus的位置
position = new THREE.Vector3, //相机最终位置
dis; //相机距离目标
duration = duration == void 0 ? 1200 : duration;
let camera = o.endCamera || viewer.scene.getActiveCamera()
let cameraPos = camera.position.clone()
let boundSize
if(o.dontChangeCamDir && (o.endYaw == void 0 || o.endPitch == void 0)){ //在俯视时仅靠dir来算不准
o.endYaw = viewer.mainViewport.view.yaw
o.endPitch = viewer.mainViewport.view.pitch
}
let moveMap = (done)=>{
if(this.mapViewer && !o.dontMoveMap){
//console.log('mapFocusOn: '+target.toArray())
const minMapWidth = o.minMapWidth || 2 //截图的时候要显示的地图范围较大,为了显示出地区名字
const minBound = new THREE.Vector2(minMapWidth,minMapWidth)//针对垂直线,在地图上只有一个点
//原始的bound
let boundOri
if(object.points){
boundOri = new THREE.Box3()
object.points.forEach(e=>{
boundOri.expandByPoint(e)
})
}else{
boundOri = object.boundingBox
}
if(o.focusBoundCenter){
boundOri.getCenter(target)
}
let boundSizeOri = boundOri.getSize(new THREE.Vector3)
let boundSizeMap = boundSizeOri.clone().multiplyScalar(o.boundExpandRatio || math.linearClamp(Math.max(boundSizeOri.x, boundSizeOri.y) , [0.5, 30], [2.5, 1.2]))//为了能同时看清测量线和地图,当测量线较短时,扩大margin,防止地图过度放大
boundSizeMap.x = Math.max(minBound.x, boundSizeMap.x )
boundSizeMap.y = Math.max(minBound.y, boundSizeMap.y )
this.mapViewer.moveTo(target.clone(), boundSizeMap, duration, o.margin, null, done)
}
}
if(o.onlyMap){
moveMap(()=>{
deferred.resolve()
})
return {promise:deferred.promise()}
}
let getPosWithFullBound = (points, boundingBox, target, cameraPos )=>{//使boundingBox差不多占满屏幕时的相机到target的距离
// points 和 boundingBox 至少有一个
let scale
if(o.dontChangeCamDir){
var inv = camera.matrixWorldInverse;
}else{
var cameraTemp = camera.clone()
let view = viewer.mainViewport.view.clone();
view.position.copy(cameraPos);
view.lookAt(target);
if(o.endPitch != void 0){
view.pitch = o.endPitch
view.yaw = o.endYaw
}
view.applyToCamera(cameraTemp)
//对镜头的bound
var inv = cameraTemp.matrixWorldInverse;
}
var bound = new THREE.Box3()
if(points){//使用points得到的bound更小 //如果points和boundingbox的差别较大,尤其使target和points中心不一致,那么points不一定会刚好在boundingbox内
points.forEach(e=>{
var p = e.clone().applyMatrix4(inv);
bound.expandByPoint(p)
})
scale = 1.2;
}else{
bound = boundingBox.applyMatrix4(inv);
scale = 1 //0.9;
}
boundSize = bound.getSize(new THREE.Vector3)
if(o.boundScale){
scale = o.boundScale
}
{
boundSize.x *= scale //稍微放大一些,不然会靠到屏幕边缘
boundSize.y *= scale
let min = 0.0001
boundSize.x = Math.max(min, boundSize.x)
boundSize.y = Math.max(min, boundSize.y)
}
if(camera.type == 'OrthographicCamera'){
dis = boundSize.length() / 2
}else{
let aspect = boundSize.x / boundSize.y
if(camera.aspect > aspect){//视野更宽则用bound的纵向来决定
dis = boundSize.y/2/ Math.tan(THREE.Math.degToRad(camera.fov / 2)) + boundSize.z/2
}else{
let hfov = cameraLight.getHFOVForCamera(camera, true);
dis = boundSize.x/2 / Math.tan(hfov / 2) + boundSize.z/2
}
dis += camera.near
}
dis = Math.max(0.1,dis)
//三个顶点以上的由于measure的中心不等于bound的中心,所以点会超出bound外。 且由于视椎近大远小,即使是两个点的,bound居中后线看上去仍旧不居中.
//获得相机最佳位置
let dir
if(o.dontChangeCamDir){
dir = viewer.mainViewport.view.direction.negate()
}else{
dir = new THREE.Vector3().subVectors(cameraPos, target).normalize()
}
if(o.dontLookUp && dir.z < 0){
dir.negate()
}
position.copy(target).add(dir.multiplyScalar(dis))
if(false){//打开以检查box
if(!this.boundBox){//调试
this.boundBox = new THREE.Mesh(new THREE.BoxGeometry(1,1,1,1));
this.boundBox.material.wireframe = true
this.boundBox.up.set(0,0,1)
Potree.Utils.setObjectLayers(this.boundBox,'sceneObjects')
this.scene.scene.add(this.boundBox);
}
this.boundBox.position.copy(target)
this.boundBox.scale.copy(boundSize)
this.boundBox.lookAt(position)
}
return position
}
if(this.images360.flying){
let f = ()=>{
this.focusOnObject(object, type, duration, $.extend(o,{deferred}))
this.images360.removeEventListener('cameraMoveDone',f)
}
this.images360.addEventListener('cameraMoveDone',f)
return {promise: deferred.promise() }
}
if (type == 'measure') {
if(object.points.length == 0){
return {promise:deferred.resolve()}
}
target.copy(object.getCenter())
//试试改变位置(方向),直视测量线。能避免倾斜角度造成的非常不居中、以及看不到面的情况
if(o.gotoBestView){//直接沿着法线方向看
if(object.points.length>2){
let facePlane = object.getFacePlane(target)
let normal = facePlane.normal.clone();
if(normal.dot(this.mainViewport.view.direction) > 0){
normal.negate() //顺着视线
}
//normal.z = Math.max(0.2, normal.z)//尽量不要仰视
cameraPos.copy(target).add(normal)
}else{
let lineDir = new THREE.Vector3().subVectors(object.points[0],object.points[1]).normalize()
let facePlane = new THREE.Plane().setFromNormalAndCoplanarPoint(lineDir, target)
//if(cameraPos.z < target.z + 1)cameraPos.z = target.z + 1//尽量不要仰视
let dir = new THREE.Vector3().subVectors(cameraPos,target).normalize()
dir.z = Math.max(dir.z, 0.4);
cameraPos.copy(target).add(dir)//尽量不要仰视
let dis = cameraPos.distanceTo(target)
dir = lineDir.clone().multiplyScalar(dis)
let line = new THREE.Line3().set(cameraPos.clone().add(dir), cameraPos.clone().add(dir.negate())) //过相机位置作到面的垂线
facePlane.intersectLine(line, cameraPos ) //得垂足,作为新的相机位置。就能在测量线中间看测量线
}
}else{
if(object.points.length>2){
let facePlane = object.getFacePlane(target)
let normal = facePlane.normal.clone()
let angle = this.mainViewport.view.direction.angleTo(normal)
let minDiff = THREE.Math.degToRad(60)
//console.log('angle',angle)
if(angle>minDiff && angleMath.PI-maxDiff){//当几乎正对时就不执行
if(angle>Math.PI/2){ //令dir和lineDir成钝角
lineDir.negate()
}
let dir = new THREE.Vector3().subVectors(camera.position, target).normalize()
let mid = new THREE.Vector3().addVectors(lineDir, dir).normalize() //中间法向量(如果刚好dir和lineDir反向,那得到的为零向量,就不移动了,但一般不会酱紫吧)
let newDir = new THREE.Vector3().addVectors(dir, mid)
cameraPos.copy(target.clone().add(newDir))
}
}else{
console.error('measure 没有facePlane points点数还不为2?')
}
}
//截图时直接飞到最佳视角,也就是平视它所在面。其余时候为了避免镜头旋转过大就算了。
position = getPosWithFullBound(object.points, null, target, cameraPos )
moveMap()
if(Potree.settings.displayMode == 'showPointCloud'){ //点云
let minDis = 0.3;
if(0 ){//如果没有被选中,会被遮挡。 2023.11.15 最好展示全局(尤其截图时),虽然被遮挡,但focus的过程是选中状态全显示的,可以看出所在范围。
let checkIntersect = ( )=>{
let intersect = this.inputHandler.ifBlockedByIntersect({point:position, cameraPos: target})// 不一定准确
if(intersect){
let blockCount = 0, unblockCount = 0, visi;
for(let i=0;i= 0.5){
visi = false
break
}
}else{
unblockCount ++;
if(unblockCount / object.points.length > 0.5){
visi = true
break
}
}
}
if(visi == void 0){
visi = unblockCount / object.points.length > 0.5
}
let shrink = ()=>{
let dir = new THREE.Vector3().subVectors(position, target).normalize().multiplyScalar(intersect.distance)
position.copy(target).add(dir)
console.log('checkIntersect newPos', position.clone() )
}
if(!visi){//更改位置距离target如果小于最小距离,需要反向。 否则直接缩短距离。
if(intersect.distance < minDis ){
console.log('检测到intersect 反向', intersect.distance )
let position1 = position.clone()
let dir = new THREE.Vector3().subVectors(position, target)
position.copy(target).sub(dir)
let intersect2 = this.inputHandler.ifBlockedByIntersect({point: position, cameraPos:target})// 不一定准确
if(intersect2){
if(intersect2.distance < intersect.distance ){
position.copy(position1)//恢复
}
shrink()
}
}else{
shrink()
}
}
}
}
checkIntersect()
//多折线没有areaPlane 有时候会看向空白区域 - -
}
}else if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准)
let target2, dir
if( object.measureType.includes('MulDistance')){//因为该线不闭合,可能看向target的方向会没有线,所以换一个target
target2 = object.points[Math.round(object.points.length / 2) ]//直接看向中间点
dir = new THREE.Vector3().subVectors(target2, position).normalize()
}
let pano = viewer.images360.fitPanoTowardPoint({
/*point : target, //不使用目标点来判断是因为缺少measure角度的信息。比如虽然可以靠近线的中心,但是线朝向屏幕,那几乎就是一个点了。
//bestDistance : dis * 0.5, //乘以小数是为了尽量靠近
boundSphere: boundOri.getBoundingSphere(new THREE.Sphere), */
target,
dir,
point : position,
bestDistance : 0 ,
checkIntersect: o.checkIntersect, gotoBestView: o.gotoBestView
})
let result = {promise:deferred.promise() }
if(pano && pano.msg){
pano = pano.pano
result.msg = pano.msg
}
if(pano){
viewer.images360.flyToPano({pano, target : target2 || target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了
if(viewer.images360.currentPano == pano){
let dis1 = viewer.images360.currentPano.position.distanceTo(target)
let dis2 = position.distanceTo(target)
//console.log('dis1 / dis2',dis1 / dis2, 'dis1-dis2', dis1-dis2)
return {msg: (dis1 / dis2 > 1.5 && dis1-dis2>10)? 'tooFar' : 'posNoChange', promise : deferred.promise() }
}
}
return result
/* if(viewer.images360.currentPano == pano){
let dis1 = viewer.images360.currentPano.position.distanceTo(target)
let dis2 = position.distanceTo(target)
//console.log('dis1 / dis2',dis1 / dis2, 'dis1-dis2', dis1-dis2)
return {msg: (dis1 / dis2 > 1.5 && dis1-dis2>10)? 'tooFar' : 'posNoChange', promise : deferred.promise() }
}else{
return {promise : deferred.promise()}
} */
//出现过到达位置后测量线标签闪烁的情况
// 测量线截图 全景 最好能放大. 但要确保该位置放大后图片加载完有点困难
}
} else if (type == 'tag' || type == 'point') {
//dimension = 1
target.copy(object.position)
let bestDistance = o.distance || 3
if(!o.dontMoveMap && this.mapViewer){
//console.log('mapFocusOn: '+target.toArray())
this.mapViewer.moveTo(target.clone(), null, duration)
}
if(Potree.settings.displayMode == 'showPointCloud'){
if(o.dontChangePos){
position.copy(cameraPos)
}else{
dis = bestDistance
let dir = o.direction ? o.direction.clone().negate() : this.mainViewport.view.direction.negate()// */new THREE.Vector3().subVectors(camera.position, target).normalize()
if(o.dontLookUp && dir.z<0) dir.z *= -1
position.copy(target).add(dir.multiplyScalar(dis))
}
if(o.sameFloor){//需要在同一楼层
let atFloor = this.modules.SiteModel.pointInWhichEntity(target, 'floor')
if(atFloor){
let camFloor = this.modules.SiteModel.pointInWhichEntity(position, 'floor')
if(camFloor != atFloor){
let raycaster = new THREE.Raycaster();
let origin = target
let dir = new THREE.Vector3().subVectors( position, target ).normalize()
raycaster.set(origin, dir)
let intersect = Potree.Utils.getIntersect(null, [atFloor.box], null, raycaster)
if(intersect){
let dis = THREE.Math.clamp(intersect.distance - 0.2, camera.near, intersect.distance)
position.addVectors(origin, dir.multiplyScalar(dis))
console.log('移动到楼层')
}else{
console.error('?no intersect?')
}
}
}
}
if(o.checkIntersect){//识别被点云遮住的话
let intersect //反向查找从target到相机的第一个intersect
intersect = this.inputHandler.ifBlockedByIntersect({point:position, margin:0, cameraPos:target} /* {point:target, margin: 0.2, cameraPos:position} */)
if(intersect){
position.copy(intersect.location)
console.log('移近')
}
}
}else if(Potree.settings.displayMode == 'showPanos'){
let pano = viewer.images360.fitPanoTowardPoint({
point : target,
dir : this.mainViewport.view.direction, //尽量不改相机方向,避免镜头晃动
checkIntersect: o.checkIntersect, sameFloor:o.sameFloor,
bestDistance, maxDis: o.maxDis //越近越好,但不要太近,bestDistance左右差不多
})
let result = {promise:deferred.promise() }
if(pano && pano.msg){
pano = pano.pano
result.msg = pano.msg
}
pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize })
return result
}
}else if(object.boundingBox && type == 'boundingBox'){//使屏幕刚好看全boundingBox
target = object.boundingBox.getCenter(new THREE.Vector3)
if(o.dir){ //指定方向
cameraPos.copy(target).sub(o.dir)
}
position = getPosWithFullBound(object.points, object.boundingBox.clone(), target, cameraPos )
if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准)
let pano = viewer.images360.fitPanoTowardPoint({
point : position,
bestDistance : 0 ,
})
let result = {promise:deferred.promise() }
if(pano && pano.msg){
pano = pano.pano
result.msg = pano.msg
}
pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了
if(!pano){
console.error('no pano')
}
return result
//出现过到达位置后测量线标签闪烁的情况
}else{
/* if(o.dontChangeCamDir){
target = null
} */
moveMap()
}
}
if(o.startCamera && o.endCamera){
viewer.mainViewport.view.tranCamera(this.mainViewport, { endPosition:position, target ,
boundSize,
callback:()=>{
//console.log('focusOnObjectSuccess: '+object.name, type)
deferred.resolve()
}, startCamera:o.startCamera, endCamera:o.endCamera, midCamera:this.scene.cameraBasic,
endYaw:o.endYaw, endPitch:o.endPitch
}, duration)
}else if(camera.type == "OrthographicCamera"){
viewer.mainViewport.view.moveOrthoCamera(this.mainViewport, { endPosition:position, target ,
boundSize,
endYaw:o.endYaw, endPitch:o.endPitch,
callback:()=>{
//console.log('focusOnObjectSuccess: '+object.name, type)
deferred.resolve()
},
}, duration)
}else{
viewer.mainViewport.view.setView({position, target, duration,
endYaw:o.endYaw, endPitch:o.endPitch,
callback:()=>{
//console.log('focusOnObjectSuccess: '+object.name, type)
deferred.resolve()
}
})
}
this.dispatchEvent({type:'focusOnObject', CamTarget:target, position}) //给controls发送信息
return {promise:deferred.promise()}
}
flyToDataset(o={}){
var pointcloud;
if(o instanceof THREE.Object3D) pointcloud = o
else if(o.pointcloud) pointcloud = o.pointcloud
else pointcloud = this.scene.pointclouds.find(p => p.dataset_id == o.id);
let duration = o.duration == void 0 ? 1000 : o.duration
if(o.focusOnPoint || !pointcloud.panosBound){//focus点云上一点,避免center区域刚好没有点
if(pointcloud.root.geometryNode){
let posArr = pointcloud.root.geometryNode.geometry.attributes.position.array
let count = pointcloud.root.geometryNode.geometry.attributes.position.count
let index = Math.ceil(count / 2) //随便取一个点
let point = new THREE.Vector3(posArr[index*3+0],posArr[index*3+1],posArr[index*3+2])
//point.applyMatrix4(pointcloud.root.pointcloud.matrixWorld)
point.applyMatrix4(pointcloud.root.sceneNode.matrixWorld)
viewer.focusOnObject({position:point},'point',duration,{dontChangeCamDir:true, distance:15})
o.dontMoveMap || viewer.mapViewer.fitToPointcloud(pointcloud, duration)
console.log('flyToDataset focusOnPoint done')
return
}
}
var center = pointcloud.bound.getCenter(new THREE.Vector3);
let position
let getPano = ()=>{//获取离中心最近的pano
let request = []
let rank = [
Images360.scoreFunctions.distanceSquared({position: center})
]
let r = Common.sortByScore(pointcloud.panos, request, rank);
if(r.length){
return r[0].item
}
}
if(Potree.settings.displayMode == 'showPanos'){
let pano = getPano()
if(pano){
if(pano == this.images360.currentPano) return 'posNoChange'
this.images360.flyToPano({
pano
})
}else return false
}else{
let target
position = center
if(pointcloud.panosBound){
let panosCenter = pointcloud.panosBound.center //pano集中的地方,也就是真正有点云的地方
position = panosCenter.clone()
/* let ratio = 0.2
position.z = center.z * ratio + panosCenter.z * (1-ratio) //因为panos一般比较低,为了不让相机朝下时看不到点云,加一丢丢中心高度
*/
let pano = getPano()
if(pano){
target = pano.position //针对像隧道一样的场景, 中心点还是panosCenter都在没有点云的地方,所以还是看向其中一个漫游点好。
position.z = target.z //水平, 避免朝上或朝下
}
}
if(this.modules.Clip.editing){
position.z = center.z //剪裁时在中心高度,因为以点云为重点
this.modules.Clip.bus.dispatchEvent({type:'flyToPos', position, duration })
}else{
if(math.closeTo(position, this.images360.position)) return 'posNoChange'
viewer.mainViewport.view.setView({position, target, duration })
}
o.dontMoveMap || viewer.mapViewer.fitToPointcloud(pointcloud, duration)
}
return true
}
findClosestDatasetOnMap(position){//寻找当前平面图上离某点最近的数据集
let pointclouds = viewer.scene.pointclouds.filter(e=>e.visible);
const addScore = viewer.bound.boundSize.length()
let r = Potree.Common.sortByScore(pointclouds, [], [(e)=>{//pos2d和bound2d距离排序
let pos3d = position.clone().setZ(e.panosBound ? e.panosBound.center.z : (e.bound.min.z+e.bound.max.z)/2)
return - (e.panosBound ? e.panosBound.bounding : e.bound).distanceToPoint(pos3d)
},(e)=>{//最有可能的是地图上显示的平面图
return e in viewer.fpVisiDatasets ? addScore : 0
}]);
return r[0] && r[0].item
}
addTimeMark(name, type){
let record = Potree.timeCollect[name]
let needRecord = record && record.start && record.measures.length < record.minCount
if(needRecord || Potree.measureTimings){
performance.mark(name+"-"+type)
if(type == 'end'){
let measure = performance.measure(name,name+"-start",name+"-end");
if(needRecord){
record.measures.push( measure.duration )
record.sum += measure.duration;
record.ave = record.sum / record.measures.length;
record.measures.sort( (a, b) => a - b );
record.median = record.measures[parseInt(record.measures.length / 2)]
}
}
}
}
addFakeMeasure(name,duration){//把一些count当做duration来统计
if(!Potree.measureTimings)return
if(!this.fakeMeasure[name]){
this.fakeMeasure[name] = []
}
let object = {
name, duration
}
this.fakeMeasure[name].push(object)
}
resolveTimings(timestamp,log){//打印用时。 注:performance手机的精度只到整数位 。 sidebar中监听update时有高cpu的函数所以不要用demo测
if(!this.toggle){
this.toggle = timestamp;
}
let duration = timestamp - this.toggle;
if(duration > 1000.0){
if(log){
let measures = performance.getEntriesByType("measure");
for(let i in this.fakeMeasure){
measures.push(...this.fakeMeasure[i])
}
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);
}
/* let glQueries = Potree.resolveQueries(this.renderer.getContext()); // resolveQueries 无
for(let [key, value] of glQueries){
let group = {
measures: value.map(v => {return {duration: v}}),
sum: value.reduce( (a, i) => a + i, 0),
n: value.length,
min: Math.min(...value),
max: Math.max(...value)
};
let groupname = `[tq] ${key}`;
groups.set(groupname, group);
names.add(groupname);
} */
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 measures = group.measures.slice()
measures.sort( (a, b) => a.duration - b.duration );
if(group.n === 1){
group.median = measures[0].duration;
}else if(group.n > 1){
group.median = measures[parseInt(group.n / 2)].duration;
}
}
let cn = Array.from(names).reduce( (a, i) => Math.max(a, i.length), 0) + 5;
let cmin = 6;
let cmed = 6;
let cmax = 6;
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(3);
let median = group.median.toFixed(3);
let max = group.max.toFixed(3);
let ave = group.mean.toFixed(3); //add
let n = group.n;
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);
}
this.fakeMeasure = {} //clear
performance.clearMarks();
performance.clearMeasures();
this.toggle = timestamp;
}
//注意,console.log本身用时挺高,降4倍时可能占用0.5毫秒,所以不能每帧都打印
}
loop(timestamp){
//let startTime = performance.now()
//console.log('间隔:' /*, parseInt((startTime - this.lastEndTime)*100 )/100 */)
if(this.paused)return
if(performance.getEntriesByName("loopWaitNext-start").length)viewer.addTimeMark('loopWaitNext','end')
if(this.stats){
this.stats.begin();
}
performance.mark('loop-start') ;// 无论有没有reportTimings都要获取,因为getBestCound需要
this.dispatchEvent('loopStart')
this.interacted = false
this.shelterCount = {byTex:0, byCloud:0, maxByTex: 100, maxByCloud:0 } //清空 因ifPointBlockedByIntersect可能在任何时候触发,所以需要一开始就定义这个,且每次计算最大可计算次数太麻烦了就定义一个吧。
let deltaTime = this.clock.getDelta()
this.update(deltaTime, timestamp);
this.magnifier.render();
this.render();
this.objs.children.forEach(e=>{
if(e.fileType == '3dTiles'){
e.runtime.update(deltaTime, this.renderer, this.mainViewport.camera)
}
})
// let vrActive = viewer.renderer.xr.isPresenting;
// if(vrActive){
// this.update(this.clock.getDelta(), timestamp);
// this.render();
// }else{
// this.update(this.clock.getDelta(), timestamp);
// this.render();
// }
Potree.framenumber++;
//-------------
this.images360.tileDownloader.update()
this.images360.panoRenderer.update()
this.images360.getNeighbours(this.interacted)
this.computeShelter()
//-------------
if(this.stats){
this.stats.end();
}
viewer.addTimeMark('loop','end')
viewer.addTimeMark('loopWaitNext','start')
this.resolveTimings(timestamp, Potree.measureTimings);
//Potree.measureTimings = 1
}
postError(content, params = {}){
let message = this.postMessage(content, params);
message.element.addClass("potree_message_error");
return message;
}
postMessage(content, params = {}){
let message = new Message(content);
let animationDuration = 100;
message.element.css("display", "none");
message.elClose.click( () => {
message.element.slideToggle(animationDuration);
let index = this.messages.indexOf(message);
if(index >= 0){
this.messages.splice(index, 1);
}
});
this.elMessages.prepend(message.element);
message.element.slideToggle(animationDuration);
this.messages.push(message);
if(params.duration !== undefined){
let fadeDuration = 500;
let slideOutDuration = 200;
setTimeout(() => {
message.element.animate({
opacity: 0
}, fadeDuration);
message.element.slideToggle(slideOutDuration);
}, params.duration)
}
return message;
}
getBoundingBox (pointclouds) {
//可以直接返回viewer.bound
if(!this.bound){
this.updateModelBound()
}
return this.bound.boundingBox.clone()//this.scene.getBoundingBox(pointclouds);
};
updateModelBound(reason){
this.boundNeedUpdate = false
this.bound = Utils.computePointcloudsBound(this.scene.pointclouds.filter(pointcloud=> //只求可见
pointcloud.visible || pointcloud.unvisibleReasons && pointcloud.unvisibleReasons.length == 1 && pointcloud.unvisibleReasons[0].reason == 'displayMode'
))
if(Potree.settings.boundAddObjs){//加上obj的bound 需要确保都updateMatrixWorld过
this.objs.children.forEach(e=>{
this.bound.boundingBox.union(e.boundingBox.clone().applyMatrix4(e.matrixWorld))
})
this.bound.boundingBox.getSize(this.bound.boundSize)
this.bound.boundingBox.getCenter(this.bound.center)
}
viewer.farWhenShowPano = this.bound.boundSize.length() * 10//全景漫游时要能看到整个skybox 原本*2的但对于距离特远的数据集需要乘大一些否则会黑面
/* let boundPlane = new THREE.Box3()
boundPlane.expandByPoint(this.bound.boundingBox.min.clone())//最低高度为bound的最低
boundPlane.expandByPoint(this.bound.boundingBox.max.clone().setZ(this.bound.center.z))//最高高度为bound的中心高度
FirstPersonControls.boundPlane = boundPlane */
//FirstPersonControls.standardSpeed = THREE.Math.clamp( Math.sqrt(this.bound.boundSize.length() )/ 100 , 0.02,0.5); //在这个boundPlane中的速度
viewer.scene.pointclouds.forEach(e=>{//海拔范围
e.material.heightMin = this.bound.boundingBox.min.z
e.material.heightMax = this.bound.boundingBox.max.z
})
this.dispatchEvent({type:'updateModelBound'})
}
waitForLoad(object, isLoadedCallback){//等待加载时显示loading。主要是贴图
this.waitQueue.push({
object,
isLoadedCallback,
})
//console.warn('waitForLoad',object.id,this.waitQueue.length)
1 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:true})
}
ifAllLoaded( ){
if(this.waitQueue.length>0){
this.waitQueue = this.waitQueue.filter(function(e) {
return !e.isLoadedCallback()
})
}
//console.warn('ifAllLoaded', this.waitQueue.length)
0 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:false})
}
cancelLoad(object){//add 突然出现还没加载完就被deactivateTiledPano但还在loading的情况,所以加了这个
this.waitQueue = this.waitQueue.filter(function(e) {
return e.object != object
})
//console.log('cancelLoad', object.id)
this.ifAllLoaded()
}
setView(o={}){
let callback = ()=>{
if(o.displayMode){
Potree.settings.displayMode = o.displayMode
}
if(!o.pano && o.currentPano )this.images360.currentPano = o.currentPano
o.callback && o.callback()
}
if(o.pano != void 0){//pano 权重高于 position
this.images360.flyToPano(o)
}else{
this.mainViewport.view.setView($.extend({},o, {callback}))
}
}
//设置点云为标准模式
setPointStandardMat(state, pointDensity, fitPointsize){
console.log('setPointStandardMat',state)
if(state){
if(this.pointStatesBefore){
return console.warn('已设置过pointStatesBefore!')
}
this.pointStatesBefore = {
opacity : new Map(),
size: new Map(),
density:Potree.settings.pointDensity,
useEDL:this.getEDLEnabled(),
shape: viewer.scene.pointclouds[0].material.shape
}
viewer.scene.pointclouds.forEach(e=>{
this.pointStatesBefore.opacity.set(e, e.temp.pointOpacity) //因为更改pointDensity时会自动变opacity,所以这项最先获取
this.pointStatesBefore.colorType = e.material.activeAttributeName;
fitPointsize && this.pointStatesBefore.size.set(e,e.temp.pointSize) //这项不一定有用,因为会被后期覆盖
})
if(pointDensity)Potree.settings.pointDensity = pointDensity //万一之后切换到全景模式怎么办
if(fitPointsize)Potree.settings.sizeFitToLevel = true
viewer.scene.pointclouds.forEach(e=>{
e.material.activeAttributeName = 'rgba';
e.material.shape = Potree.PointShape['SQUARE']
fitPointsize && e.changePointSize(Potree.config.material.realPointSize, true)
e.changePointOpacity(1)
})
viewer.setEDLEnabled(false)
}else{
if(!this.pointStatesBefore){
return console.error('未设置过pointStatesBefore!')
}
Potree.settings.sizeFitToLevel = false
if(pointDensity)Potree.settings.pointDensity = this.pointStatesBefore.pointDensity
viewer.scene.pointclouds.forEach(e=>{
e.material.activeAttributeName = this.pointStatesBefore.colorType
e.changePointOpacity(this.pointStatesBefore.opacity.get(e))
e.material.shape = this.pointStatesBefore.shape
let size = this.pointStatesBefore.size.get(e)
if(size) e.changePointSize(size)
})
viewer.setEDLEnabled(this.pointStatesBefore.useEDL)
this.pointStatesBefore = null
}
}
//调试时显示transformControl来调节object
transformObject(object){
let seleted = viewer.inputHandler.selection[0]
if(!object){//取消
seleted && viewer.inputHandler.toggleSelection(seleted);
return
}
if(seleted && seleted != object){//要更换,先取消
this.transformObject(null)
}
if(!object.boundingBox){
object.boundingBox = new THREE.Box3() //任意大小 只是为了显示黄色外框
//??? computeBoundingBox
}
if(!viewer.inputHandler.selection.includes(object)){
viewer.inputHandler.toggleSelection(object);
}
}
pointInWhichPointcloud(pos){//选择最接近中心的那个 使用boundSphere
let result = Common.sortByScore(this.scene.pointclouds,[],[
(pointcloud)=>{
var size = pointcloud.pcoGeometry.tightBoundingBox.getSize(new THREE.Vector3)
var center = pointcloud.bound.getCenter(new THREE.Vector3)
var length = size.length() / 2
var dis = pos.distanceTo(center);
return length / dis //到数据集中心的距离占数据集大小越小越好
}
])
//若要求更准确的话,可以使用ifContainsPoint判断一下是否在bound中
let r = result[0];
return r && r.score > 1 ? result[0].item : null
}
/* createRoomEv(){
const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator( this.renderer );
}
*/
modelLoaded(object, fileInfo_={}, done){//普通模型加载完以后
object.isModel = true
let boundingBox = new THREE.Box3()
if(fileInfo_.parentInfo){
object.name = fileInfo_.name
fileInfo_.parentInfo.loadedCount ++
fileInfo_.parentInfo.modelGroup.add(object)
if(fileInfo_.parentInfo.loadedCount == fileInfo_.parentInfo.url.length){
return loadDone(fileInfo_.parentInfo.modelGroup, fileInfo_.parentInfo)
}else{
return
}
}
object.name = fileInfo_.name != void 0 ? fileInfo_.name : fileInfo_.type
object.fileType = fileInfo_.fileType
object.boundingBox = boundingBox //未乘上matrixWorld的本地boundingBox
//object.scale.set(1,1,1);//先获取原始的大小时的boundingBox
object.opacity = 1 //初始化 记录
object.updateMatrixWorld()
if(fileInfo_.id != void 0)object.dataset_id = fileInfo_.id
fileInfo_.loadCostTime = Date.now() - fileInfo_.loadStartTime
/* let weight = Math.round((total / 1024 / 1024) * 100) / 100;*/
console.log( '加载完毕:', Common.getNameFromURL(fileInfo_.url), '耗时(ms)', fileInfo_.loadCostTime, /* 模型数据量:' + weight + 'M' */)
if(fileInfo_.fileType == '3dTiles'){
let tileset = object.runtime.getTileset()
//TileHeader: tileset.root
//参见另一个工程 TileRenderer.js preprocessNode //这个坐标位置几万…… let data = boundingVolume.halfAxes //但这个似乎是premultiply( transform );过后的,可能需还原下
let json = tileset.tileset
let box = json.root.boundingVolume.box
if(box){
let center = new THREE.Vector3(box[0],box[1],box[2])
let boundSize = new THREE.Vector3( )
// get the extents of the bounds in each axis
let vecX = new THREE.Vector3( box[ 3 ], box[ 4 ], box[ 5 ] )
let vecY = new THREE.Vector3( box[ 6 ], box[ 7 ], box[ 8 ] );
let vecZ = new THREE.Vector3( box[ 9 ], box[ 10 ], box[ 11 ] );
const scaleX = vecX.length();
const scaleY = vecY.length();
const scaleZ = vecZ.length();
/* boundingBox.expandByPoint(center);
boundingBox.expandByVector(new THREE.Vector3(scaleX,scaleY,scaleZ)) */
boundingBox.min.set( - scaleX, - scaleY, - scaleZ );
boundingBox.max.set( scaleX, scaleY, scaleZ );
}else if(json.root.boundingVolume.sphere){
let sphereData = json.root.boundingVolume.sphere
let center = new THREE.Vector3(...sphereData)
let radius = sphereData[3] / 2
/* let sphere = new THREE.Sphere(center, radius)
let box = sphere.getBoundingBox()
boundingBox.copy(box) */
boundingBox.min.set( - radius, - radius, - radius );
boundingBox.max.set( radius, radius, radius );
}else{
return console.error('json boundingVolume 缺少信息')
}
//中心点居然没用。可能是漏用了什么信息,也许这和LVBADUI_qp是散的有关。
console.log('3d tiles json',json)
json.root.refine = 'ADD';
json.refine = 'ADD';
}else {
Potree.Utils.setObjectLayers(object,'model')
object.traverse( ( child )=>{
let is = child.isMesh || child instanceof THREE.Points || child.isLine
if (is){
child.renderOrder = Potree.config.renderOrders.model;
//if(Potree.settings.boundAddObjs){
child.geometry.computeBoundingBox()
//console.log(child.matrixWorld.clone())
boundingBox.union(child.geometry.boundingBox.clone().applyMatrix4(child.matrixWorld)) //但感觉如果最外层object大小不为1,要还原下scale再乘
//}//获取在scale为1时,表现出的大小
//Potree.Utils.makeTexDontResize(child.material.map)
//console.log(child.name, 'roughness',child.material.roughness,'metalness',child.material.metalness)
//暂时用这种材质:
if(fileInfo_.unlit && (!(child.material instanceof THREE.MeshBasicMaterial) || object.fileType == 'glb')){
//let material = new THREE.MeshBasicMaterial({map:child.material.map})
let material = new BasicMaterial({map : child.material.map}) //很奇怪glb的图会使原本的MeshBasicMaterial 会偏暗,所以自己重新写
//child.material.dispose()
child.material = material
}
if(fileInfo_.useStandandMat && !(child.material instanceof THREE.MeshStandardMaterial)){
child.material = new THREE.MeshStandardMaterial()
}
if(child.material instanceof THREE.MeshStandardMaterial){
child.material.roughness = 0.7
child.material.metalness = 0.5
}
}
} );
}
this.objs.add(object)
if(fileInfo_.transform){
let setTransfrom = (name)=>{
let value = fileInfo_.transform[name]
if(!value)return
if(value instanceof Array){
object[name].fromArray(value)
}else{
object[name].copy(value)
}
}
setTransfrom('position')
setTransfrom('rotation')
setTransfrom('scale')
}
if(fileInfo_.moveWithPointcloud){
object.updateMatrix();
object.matrixAutoUpdate = false
object.matrix.premultiply(viewer.scene.pointclouds[0].transformMatrix) //默认跟随第一个数据集
object.matrixWorldNeedsUpdate = true
}
object.updateMatrixWorld()
MergeEditor.getBoundCenter(object) //初始化
done && done(object, fileInfo_)
this.dispatchEvent({type:'modelLoaded',model:object})
}
async loadModel(fileInfo, done, onProgress_, onError){
console.log('开始加载', Common.getNameFromURL(fileInfo.url) )
let boundingBox = new THREE.Box3()
/* if(!Potree.settings.boundAddObjs){
boundingBox.min.set(-0.5,-0.5,-0.5); boundingBox.max.set(0.5,0.5,0.5)
} */
if(fileInfo.objurl){
fileInfo.url = fileInfo.objurl, fileInfo.fileType = 'obj' //兼容最早的
}
if(fileInfo.url instanceof Array){
if(fileInfo.url.length == 1){
fileInfo.url = fileInfo.url[0]
}else{
fileInfo.loadedCount = 0
fileInfo.modelGroup = new THREE.Object3D; //parentGroup.name = fileInfo.title
fileInfo.url.forEach((url,i)=>{
let fileInfoS = Common.CloneObject(fileInfo)
fileInfoS.url = url
fileInfoS.name = 'child-'+i
fileInfoS.parentInfo = fileInfo
this.loadModel(fileInfoS, done, onProgress_, onError)
})
return
}
}
fileInfo.url = Common.dealURL(fileInfo.url) //去除'+'
fileInfo.loadStartTime = Date.now()
//let fileType = fileInfo.tilesUrl ? '3dTiles' : fileInfo.objurl ? 'obj' : 'glb'
let loadDone = (object, fileInfo_ )=>{
this.modelLoaded(object, fileInfo_ || fileInfo , done)
}
let onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
let percentComplete = xhr.loaded / xhr.total * 100;
//console.log( Math.round(percentComplete, 2) + '% downloaded' );
onProgress_ && onProgress_(percentComplete)
}
};
if(fileInfo.fileType == 'obj'){ //暂时不支持数组
if(fileInfo.mtlurl){
loaders.mtlLoader.load( fileInfo.mtlurl , (materials)=>{
materials.preload();
loaders.objLoader.setMaterials( materials ).load(fileInfo.objurl, (object, total)=>{
loadDone(object/* , total, fileInfo.objurl */)
}, onProgress, onError )
} , onProgress, onError );
}else{
loaders.objLoader.load(fileInfo.objurl, (object, total)=>{
loadDone(object)
}, onProgress, onError)
}
}else if(fileInfo.fileType == 'glb'){
loaders.glbLoader.unlitMat = true//!!fileInfo.unlit
loaders.glbLoader.load(fileInfo.url, ( gltf, total )=>{
//console.log('loadGLTF', gltf)
loadDone(gltf.scene/* , total, fileInfo.url */)
}, onProgress, onError)
}else if(fileInfo.fileType == 'ply'){
loaders.plyLoader.load( fileInfo.url, (geometry) => {
let object
console.log('ply加载完毕', geometry)
if(!geometry.index){//点云
object = new THREE.Points(geometry, new THREE.PointsMaterial({vertexColors:true, size:0.02}))
//141M的点云,intersect费时300ms以上
}else{//mesh
object = new THREE.Mesh(geometry)
}
loadDone(object)
})
}else if(fileInfo.fileType == '3dTiles'){
let result = await Loader3DTiles.load({
url: fileInfo.url,
gltfLoader : loaders.glbLoader,
//renderer: SceneRenderer.renderer
options: {
//dracoDecoderPath: '../utils/loaders/DRACOLoader/draco',
//basisTranscoderPath: '../utils/loaders/KTX2Loader/basis',
maximumScreenSpaceError: 30, //如果本身tiles很密很小这个值就不能很大。
//maxDepth: 100,
maximumMemoryUsage: 200, //缓存大小。单位M(但实际结果是 2.5*maximumMemoryUsage + 750 。超过2G会崩, 所以应该小于540) 若太小,密集的tile反复加载很卡. (任务管理器刷新网页后若内存不掉就要结束进程否则虚高)
//debug:true,
parent: this.scene.scene,
is4dkk: fileInfo.is4dkk,//是否是4dkk中的模型
updateTime: fileInfo.updateTime, //加后缀防止缓存
},
})
console.log(result)
result.model.runtime = result.runtime
let loaded = false
let tileset = result.runtime.getTileset()
tileset.addEventListener('endTileLoading', function (data) {//Tileset3D
if (data.loadingCount == 0 && !loaded) {
loaded = true;
console.log('loaded!!!!!!!!!!!!!')
}
});
tileset.addEventListener('tileLoaded',(e)=>{ //每一个tile加载完要更改透明度
let opacity = result.model.opacity
MergeEditor.changeOpacity(e.tileContent,opacity)
//set Layers ?
})
{
let vi = true
Object.defineProperty( result.model, "visible", {
get: function() {
return vi
},
set: function(v) {
vi = v
tileset.visible = v; //同步,使不加载
tileset.nextForceUpdate = true
}
})
}
loadDone(result.model/* , null, fileInfo.url */)
}else if(fileInfo.fileType == 'dxf'){
loaders.dxfLoader.load(fileInfo.url,(object)=>{
loadDone(object)
},fileInfo)
}
}
removeModel(model){
this.objs.remove(model);
let dispose = (e)=>{
e.geometry && e.geometry.dispose()
e.material && e.material.dispose()
}
model.traverse(e=>{
dispose(e)
})
if(Potree.settings.boundAddObjs){
this.updateModelBound()
}
}
setDisplay(state, cause='setDisplay'){//如果创建了iframe,主页的需要隐藏的话需要释放一些内存出来。iframe关闭前也释放下比较保险
state = !!state
this.objs.children.forEach(e=>{
if(e.fileType == '3dTiles'){
let tileset = e.runtime.getTileset()
Potree.Utils.updateVisible(e, cause, state)
if(!state) tileset._cache.trim(); //使下一次update时dispose所有不可见的tiles
e.runtime.update(16, this.renderer, this.mainViewport.camera, true)
if(state) this.dispatchEvent('content_changed')
}
})
if(state){
Potree.pointBudget = 6*1000*1000 //先随便写一个, 随后mergeEditor.updateMemoryUsage
}else{
Potree.pointBudget = 0
Potree.updatePointClouds(this.scene.pointclouds, this.mainViewport.camera, this.mainViewport.resolution )
this.images360.panoRenderer.disposeIdelTargets() //如果也能清空当前使用的就好了,但是恢复就需要时间
this.images360.depthSampler.clearTexData()
}
this.dispatchEvent({type:'setDisplay',state})
this.paused = !state
}
addFire(){
if(Potree.settings.number == 't-CwfhfqJ'){
let position = Potree.Utils.datasetPosTransform({
pointcloud:viewer.scene.pointclouds[0],
position: new THREE.Vector3(4.4318,-0.580291847759, -0.78),
fromDataset:true
})
viewer.modules.ParticleEditor.addParticle( {
type:'fire',
positions:[position],
radius:0.42,
height:10,
})
viewer.modules.ParticleEditor.addParticle( {
type:'smoke',
positions: [ new THREE.Vector3().addVectors(position,new THREE.Vector3(0,0,0.3))],
positionStyle : 'sphere' ,
positionRadius : 0.3,
sizeTween: [[0, 0.3, 0.9, 1], [0.05, 0.1, 1, 0.8]],
opacityBase : 0.2,
opacityTween :[ [0, 0.3, 0.7, 0.95, 1], [0, 0.2, 1 , 0.1, 0] ],
velocityBase : new THREE.Vector3( 0, 0, 1),
velocitySpread : new THREE.Vector3( 0.2, 0.2, -0.3),
accelerationBase : 0.2,
accelerationSpread : 0.7,
radius:0,
//particlesPerSecond : 30,
particleDeathAge : 3.0,
})
viewer.modules.ParticleEditor.addParticle( {
type:'explode',
name:'fire splash',
position: new THREE.Vector3().addVectors(position,new THREE.Vector3(0,0,0.3)),
size: 0.1,
sizeRange: 0.3,
sizeTween:[[0, 0.05, 0.3, 0.45], [0, 0.02, 0.1, 0.05] ],
opacityTween: [[0, 0.05, 0.3, 0.45], [1, 1, 0.5, 0]] ,
speed : 1, //sphere
speedRange : 4,
radius: 0.1,
acceleration : 0.3,
accelerationRange : 1,
particleSpaceTime:0,
strength:4,
})
}
}
addVideo(){
if(Potree.settings.number != 'SS-t-P6zBR73Gke')return
var geo = new THREE.PlaneGeometry(1, 1, 1, 1)
var videoInfo = this.videoInfo = [
{
id: '40-2',
url: 'https://laser-oss.4dkankan.com/testdata/SS-t-P6zBR73Gke/temp/poi/2022/05/10/0aabafee-36b8-455d-9c11-0780bf694786.mp4',
rotation:[-1.494468618954883, -1.4987317433158989, -3.061254983446741],
position:[ 19.801820617361624, 2.884673619844108, -0.03362305858221648],
scale:[3.5741423153151763, 2.8738725275578703, 1],
},
{
id: 40,
/* rotation:[-1.534692822378723, 0.01083403560862361, 3.141535283661569],
position:[17.2934294239949861, 2.413510747928117, -0.008057029580231356], */
url: 'https://laser-oss.4dkankan.com/testdata/SS-t-P6zBR73Gke/temp/poi/2022/05/09/7896d6ef-a2d6-4fd7-949c-768782a5b484.mp4',
rotation:[-1.5487684197910518, 0.021848470169552752, -3.1387534893955236],
position:[17.277316608096, 2.0840432922115846, -0.0931149415437065],
scale:[2.0821757723834047, 0.6129478480765236, 1],
visibles: [40]
},
]
let add = (info)=>{
var video = $(``)[0]
video.setAttribute("crossOrigin", 'Anonymous')
video.src = info.url || Potree.resourcePath+`/video/${Potree.settings.number}/${info.id}.mp4`
var map = new THREE.VideoTexture(video);
var plane = this.videoPlane = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({
color:"#ffffff",
transparent: !0,
depthTest:false,
opacity:0 ,
//side:2,
map
}))
plane.position.fromArray(info.position)
plane.rotation.fromArray(info.rotation)
info.scale && plane.scale.fromArray(info.scale)
this.scene.scene.add(plane)
info.plane = plane
plane.boundingBox = new THREE.Box3(new THREE.Vector3(0,-0.5,0),new THREE.Vector3(1,-0.4,0.2))
video.addEventListener('loadeddata', function(e) {
video.play()
if(!info.visibles/* ||!viewer.images360.currentPano || info.visibles.includes(viewer.images360.currentPano.id) */){
plane.material.opacity = 1
}
info.scale || plane.scale.set(video.videoWidth/1000,video.videoHeight/1000,1) // 1080 * 1920
console.log('video loadeddata', info.id)
})
if(info.visibles){
this.images360.addEventListener('flyToPano' ,(e)=>{//飞之前
if(info.visibles.includes(e.toPano.pano.id)){ //出现
setTimeout(()=>{
plane.visible = true;
video.currentTime = 0
video.play();
if(video.paused){
var startPlay = ()=>{
plane.visible && video.play()
this.removeEventListener('global_mousedown', startPlay)
}
this.addEventListener('global_mousedown', startPlay)
}
Potree.settings.zoom.enabled = false
transitions.start(lerp.property(plane.material, "opacity", 1 ) , e.toPano.duration*0.4 , ()=>{
}, 0, easing['easeInOutQuad'])
}, e.toPano.duration*0.6) //时间上不能和消失的重叠 延迟
}else{
//消失
transitions.start(lerp.property(plane.material, "opacity", 0, ) , e.toPano.duration*0.4, ()=>{
if(!info){
plane.visible = false
video.pause()
Potree.settings.zoom.enabled = true
}
}, 0, easing['easeInOutQuad'])
}
})
}
var startPlay = ()=>{
video.play()
//video.pause()
//video.currentTime = 0.1;
this.removeEventListener('global_mousedown', startPlay)
}
this.addEventListener('global_mousedown', startPlay)
Potree.settings.isTest && plane.addEventListener('select',(e)=>{console.log(e)})
}
videoInfo.forEach(info=>{
add(info)
})
/* this.images360.addEventListener('flyToPano' ,(e)=>{//飞之前
if(Potree.settings.displayMode != 'showPanos') return
let info = videoInfo[e.toPano.pano.id]
if(info ){ //出现
setTimeout(()=>{
plane.visible = true;
plane.position.fromArray(info.position)
plane.rotation.fromArray(info.rotation)
video.src = Potree.resourcePath+`/video/${Potree.settings.number}/${e.toPano.pano.id}.mp4`
video.play();
video.currentTime = 0
Potree.settings.zoom.enabled = false
transitions.start(lerp.property(plane.material, "opacity", 1 ) , e.toPano.duration*0.4 , ()=>{
}, 0, easing['easeInOutQuad'])
}, e.toPano.duration*0.6) //时间上不能和消失的重叠 延迟
}
//消失
transitions.start(lerp.property(plane.material, "opacity", 0, ) , e.toPano.duration*0.4, ()=>{
if(!info){
plane.visible = false
video.pause()
Potree.settings.zoom.enabled = true
}
}, 0, easing['easeInOutQuad'])
})
this.images360.addEventListener('endChangeMode',(e)=>{ //暂时不处理初始加载时就在有视频的点位上的情况
if(e.mode == 'showPanos'){
let info = videoInfo[this.images360.currentPano.id]
if(info ){ //出现
plane.visible = true;
plane.position.fromArray(info.position)
plane.rotation.fromArray(info.rotation)
plane.material.opacity = 0
video.src = Potree.resourcePath+`/video/${Potree.settings.number}/${this.images360.currentPano.id}.mp4`
video.play();
video.currentTime = 0
Potree.settings.zoom.enabled = false
transitions.start(lerp.property(plane.material, "opacity", 1, (e)=>{console.log('fadeIn',e)}) , 300 , ()=>{
}, 0, easing['easeInOutQuad'])
}
}else{
plane.visible = false;
Potree.settings.zoom.enabled = true
}
})
*/
}
/* addTube(){//加水管 自动生成
if(Potree.settings.number == 't-8KbK1JjubE'){
let boundingBox = new THREE.Box3()
boundingBox.min.set(-1,-1,-1); boundingBox.max.set(1,1,1)
let radius = 0.08;
let radialSegments = 5
let radSegments = Math.PI*2 / radialSegments
var circlePts = [];//横截面
for(let i=0;i{//height:在path之上的高度,负数代表在path之下
var name = 'cylinder'+count
var mat = new THREE.MeshStandardMaterial({color, depthTest:false, roughness:0.4,metalness:0.5})
let linePath = path.map(e=>new THREE.Vector3().copy(e).setZ(e.z+height))
let geo = MeshDraw.getExtrudeGeo( circlePts, null,{ extrudePath:linePath, tension:0.2} )
var mesh = new THREE.Mesh(geo,mat);
mesh.name = name
window[name] = mesh
mesh.boundingBox = boundingBox
mesh.matrixAutoUpdate = false
mesh.matrix.copy(viewer.scene.pointclouds[0].transformMatrix)
mesh.matrixWorldNeedsUpdate = true
this.scene.scene.add(mesh);
count ++
}
let linePath, height
//地上管子 黄色
linePath = [{"x":-109.83,"y":-68.33,"z":-7.52},{"x":-95.17,"y":-59.3,"z":-7.38}, {"x":-38.75,"y":-24.01,"z":-6.01},{"x":0.5,"y":0.19,"z":-3.89},{"x":39.29,"y":24.41,"z":-1.31}
,{"x":43.58,"y":27.7,"z":-0.97},{"x":40.22,"y":35.37,"z":-0.67}// 拐弯向右
, {"x":39.18,"y":36.71,"z":0.35},{"x":38.69,"y":36.04,"z":18.04} // 拐弯向上
]
height = radius + 0.05;
addMesh('#b86', linePath, height)
//地下管子 藍色
linePath = [{"x":-108.24,"y":-70.61,"z":-7.52}, {"x":-57.8,"y":-39.31,"z":-6.72},{"x":-18.8,"y":-15.35,"z":-5.01},{"x":55.87,"y":31.67,"z":-0.04},{"x":110.53,"y":66.48,"z":5.14}
]
height = -0.5;
addMesh('#48a', linePath, height)
}
} */
addTube(datas){//加水管 自动生成
for(let number in datas){
if(number == Potree.settings.number){
let data = datas[number]
let boundingBox = new THREE.Box3()
boundingBox.min.set(-1,-1,-1); boundingBox.max.set(1,1,1)
var index = 0
var addMesh = ({color, path, height,fromDataset, radius=0.08, datasetId=Potree.settings.originDatasetId, spaceDis, tension=0.1, visiEntity}={})=>{//height:在path之上的高度,负数代表在path之下
var name = 'tube_'+index
let radialSegments = 30//THREE.Math.clamp( Math.round(radius * 800), 10, 40)
let radSegments = Math.PI*2 / radialSegments
var circlePts = [];//横截面
for(let i=0;i{
e instanceof Array && (e = new THREE.Vector3().fromArray(e))
let pos
if(fromDataset){
//获取:JSON.stringify(viewer.scene.measurements[0].dataset_points.map(e=>Potree.math.toPrecision(e.toArray(),3)))
pos = Potree.Utils.datasetPosTransform({ fromDataset: true, position:e, datasetId})
}else{
pos = new THREE.Vector3().copy(e)
}
return pos.setZ(pos.z+height)
})
console.log(linePath)
let geo = MeshDraw.getExtrudeGeo( circlePts, null,{ extrudePath:linePath, tension, spaceDis} )
var mesh = new THREE.Mesh(geo,mat);
mesh.name = name
window[name] = mesh
mesh.boundingBox = boundingBox
mesh.matrixAutoUpdate = false
fromDataset || mesh.matrix.copy(viewer.scene.pointclouds[0].transformMatrix)
mesh.matrixWorldNeedsUpdate = true
this.scene.scene.add(mesh);
if(visiEntity){
this.modules.SiteModel.bus.addEventListener('buildingChange',(e)=>{
Potree.Utils.updateVisible(mesh,'isInEntity', e.entity && e.entity.name == visiEntity)
})
}
}
while(index < data.length){
addMesh(data[index])
index++
}
}
}
/*
可能用到的
viewer.scene.measurements.forEach(e=>e.edgeLabels.forEach(e=>Potree.Utils.updateVisible(e,'f',false)))
JSON.stringify(viewer.scene.measurements.find(e=>e.visible).dataset_points.map(e=>Potree.math.toPrecision(e.toArray(),3)))
*/
}
addSprite(e){//api
let sprite
if(e.text != void 0){
sprite = new TextSprite(e )
}else{
let map = texLoader.load(src)
e.map = map
sprite = new Sprite(e )
}
return sprite
}
};
//------ CLIP 默认clipTask都是clipInside ----------------------
/*
并集相当于加法,交集相当于乘法。 所有结果都能展开成多个乘积相加。
假设有4个clipBoxes,ABCD, 如果是 A*B + C*D ,那么这是最终结果。 如果是 (A+B)*(C+D) = A*C+A*D+B*C+B*D
*/
/*
let Clips = {
boxes : [],
unionGroups : [], //二维数组。最外层要求并集,里层要求交集(如果只有一个元素就是本身)。总结起来就是要求一堆交集的并集
shaderParams:{},
needsUpdate : true,
addClip(box, clipMethod){
//不允许重复
if(this.boxes.includes(box)){
return console.warn('addClip重复添加了box',box)
}
boxes.push(box)
if(clipMethod == 'any'){//并
this.unionGroups.push([box])
}else if(clipMethod == 'all'){//交
this.unionGroups.forEach(mixGroup=>mixGroup.push(box))
}
this.needsUpdate = true
},
removeClip(box){
if(!this.boxes.includes(box)){
return console.warn('removeClip没有找到该box',box)
}
var newGroups = [];
this.unionGroups.forEach(mixGroup=>{
if(mixGroup.length == 1 && mixGroup[0] == box)return;//直接删除
newGroups.push(mixGroup.filter(e=>e!=box));
})
this.unionGroups = newGroups;
this.needsUpdate = true
}
,
clearClip(){
this.boxes = [];
this.unionGroups = []
this.needsUpdate = true
}
,
updateShaderParams(){//没写完 - - 见 pointcloud clip.vs
//uniform mat4 clipBoxes[num_clipboxes];
//uniform int clipBoxGroupCount;
//uniform int mixClipIndices[clipboxGroupItemCount]; //把所有的要求都直接放到数组内
//这里需要转为Float32Array..? 参考material.setClipBoxes
let everyClipGroupCount = this.unionGroups.map(e=>e.length)
let mixClipIndices = []
this.unionGroups.forEach(e=>{
mixClipIndices.push(...e)
})
this.shaderParams = {
num_clipboxes : this.boxes.length,
clipBoxGroupCount : this.unionGroups.length,
everyClipGroupCount,
clipBoxIndexCount: mixClipIndices.length,
mixClipIndices
}
}
,
getShaderParams(){//每次要传递参数到shader中,执行这个就好
if(this.needsUpdate){
this.updateShaderParams()
}
return this.shaderParams
}
}
*/