import * as THREE from "../../../libs/three.js/build/three.module.js"; import {LineDraw, MeshDraw} from "../utils/DrawUtil.js"; import {TextSprite} from './TextSprite.js' import Sprite from './Sprite.js' import DepthBasicMaterial from "../materials/DepthBasicMaterial.js"; import CursorDeal from "../utils/CursorDeal.js"; const depthMatProp = { //为了防止拉远后因放大而一半嵌入墙。 useDepth : true , startClipDis : 0.5, clipDistance : 1,//消失距离 startOcclusDis: 0.5, occlusionDistance: 0.9,//变为backColor距离 maxOcclusionFactor:0.7, maxClipFactor:1 } const planeGeo = new THREE.PlaneBufferGeometry(1,1) let texLoader = new THREE.TextureLoader() let lineMat, dragPointMat const defaultLineLength = 1 const defaultSpotScale = 0.35 const titleHeight = {uponSpot:0.1 }//title底部和spot顶端间隔 const Vectors = { UP : new THREE.Vector3(0,1,0), ZERO: new THREE.Vector3() } class Tag extends THREE.Shim.FollowRootObject{ constructor(o){ super(o.root) this.title = o.title this.fontsize = o.fontsize this.lineLength = o.lineLength != void 0 ? o.lineLength : defaultLineLength this.position.copy(o.position) this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0, 1) this.build(o) this.bindEvent() this.dragEnable = true } set dragEnable(state){ this.lineDragPoint.visible = state } get dragEnable(){ return this.lineDragPoint.visible } build(o){ lineMat || (lineMat = LineDraw.createFatLineMat(Object.assign({},depthMatProp, { color: '#ffffff', useDepth :true, lineWidth: 1 }))) let group = new THREE.Object3D() this.spot = new THREE.Mesh(planeGeo, new DepthBasicMaterial(Object.assign({},depthMatProp,{ transparent:true, }))) this.spot.name = 'spot' this.spot.scale.set(defaultSpotScale,defaultSpotScale,defaultSpotScale) this.spot.renderOrder = this.spot.pickOrder = Potree.config.renderOrders.tag.spot; Potree.settings.isOfficial || this.changeMap(Potree.resourcePath+'/textures/spot_default.png') this.line = LineDraw.createFatLine([], {mat:lineMat}) this.line.name = 'tagLine' this.line.renderOrder = this.line.pickOrder = Potree.config.renderOrders.tag.line; this.titleLabel = new TextSprite(Object.assign({},depthMatProp,{ root: group, text:'', sizeInfo:{width2d:150}, textColor:{r:255,g:255,b:255,a:1.0}, backgroundColor:{r:0,g:0,b:0,a:0.7}, borderRadius: 6, fontsize: this.fontsize || 14, fontWeight:'',//thick renderOrder : Potree.config.renderOrders.tag.label, pickOrder: Potree.config.renderOrders.tag.label, useDepth : true , maxLineWidth: 300, transform2Dpercent:{x:0,y:0.5}, //向上移动一半 textAlign: Potree.settings.isOfficial && 'left' })) //更新sprite时,实际更新的是root: spot的矩阵 this.setTitle(this.title) this.updateTitlePos() group.add(this.titleLabel) group.add(this.spot) this.add(group); this.add(this.line) viewer.tags.add(this) if(!dragPointMat){ let map = texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png',()=>{}) dragPointMat = { default: new THREE.MeshBasicMaterial({ map, transparent:true, color:'#0fe', opacity:0, depthTest:false, }), hover: new THREE.MeshBasicMaterial({ map, transparent:true, color:'#0fe', opacity:0.4, depthTest:false, }) } } this.lineDragPoint = new THREE.Mesh(planeGeo, dragPointMat.default) //修改线高度时出现的小圆点 this.lineDragPoint.scale.set(0.15,0.15,0.15); this.lineDragPoint.name = 'lineDragPoint' this.lineDragPoint.renderOrder = this.lineDragPoint.pickOrder = Potree.config.renderOrders.tag.spot + 3; group.add(this.lineDragPoint) this.updatePose() } bindEvent(){ let hoverState = {}, grabbingObject let setDragPointState = (state)=>{ this.lineDragPoint.material = state ? dragPointMat.hover : dragPointMat.default this.spot.material.opacity = state ? 0.5 : 1 this.titleLabel.sprite.material.opacity = state ? 0.5 : 1 } { //因为只有有intersect时才能拖拽,所以写得比较麻烦 let cursor = {hoverGrab:0, grabbing:0} let setCursor = (name, action)=>{ let state = action == 'add' ? 1 : 0 if(state != cursor[name]){ cursor[name] = state viewer.dispatchEvent({ type : "CursorChange", action, name }) } } [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('mousemove',(e)=>{ hoverState[e.target.name] = 1 if(this.dragEnable && (viewer.inputHandler.intersect || hoverState['lineDragPoint'])){//能拖拽时 setCursor('hoverGrab', 'add') }else{ setCursor('hoverGrab', 'remove') } })); [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('mouseleave',(e)=>{ hoverState[e.target.name] = 0 if(!Object.values(hoverState).some(e=>e)){//都没hover才取消 setCursor('hoverGrab', 'remove') } /* if(!hoverState.line && !hoverState.spot && !hoverState.label){ this.dispatchEvent('mouseleave') } */ })); [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('drag',(e)=>{ if(this.dragEnable && cursor.grabbing){ if(e.target.name == 'lineDragPoint'){ this.dragLineLen(e) }else{ let info = viewer.tagTool.getPoseByIntersect(e) info && this.changePos(info) } } })); [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('startDragging',(e)=>{ this.dragEnable && (viewer.inputHandler.intersect || e.target.name == 'lineDragPoint') && setCursor('grabbing', 'add') grabbingObject = e.target.name grabbingObject == 'lineDragPoint' && setDragPointState(true) })); [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('drop',(e)=>{ this.dragEnable && setCursor('grabbing', 'remove') grabbingObject = null hoverState['lineDragPoint'] || setDragPointState(false) })); //拖拽线来移动。虽然理想方式是拟真,拖拽时不改变在线上的位置,使之平移,但仔细想想似乎办不到。因为墙面normal是不固定的,尤其在交界处难以确定。不知鼠标在空中的位置,即使是平行镜头移动也无法满足所有情况。matterport是加了底座,移动也是改变底座中心。 } { let mouseover = (e)=>{ this.dispatchEvent('mouseover') } let mouseleave = (e)=>{ //if(!hoverState.line && !hoverState.spot && !hoverState.label){ this.dispatchEvent('mouseleave') //} } let click = (e)=>{ this.dispatchEvent('click') } this.spot.addEventListener('mouseover',mouseover) this.spot.addEventListener('mouseleave',mouseleave) this.titleLabel.addEventListener('mouseover',mouseover) this.titleLabel.addEventListener('mouseleave',mouseleave) this.spot.addEventListener('click',click) this.titleLabel.addEventListener('click',click) } this.titleLabel.sprite.addEventListener('spriteUpdated',()=>{ this.updateDepthParams() }) //-----------set line length // CursorDeal this.lineDragPoint.addEventListener('mouseover',(e)=>{ grabbingObject || setDragPointState(true) }) this.lineDragPoint.addEventListener('mouseleave',(e)=>{ grabbingObject != 'lineDragPoint' && setDragPointState(false) }) } updateDepthParams(){//为了避免热点嵌入墙壁,实时根据其大小更新材质系数。 但是在倾斜的角度看由于遮挡距离很大肯定会嵌入的 let s = this.titleLabel.parent.scale.x let names = ['clipDistance', 'occlusionDistance', 'startClipDis', 'startOcclusDis'] let titleSize = Math.max(this.titleLabel.sprite.scale.x, this.titleLabel.sprite.scale.y) * s names.forEach(name=>{ this.titleLabel.sprite.material.uniforms[name].value = depthMatProp[name] * titleSize }) if(this.onMesh){//not sprite,还原。 大概能不被崎岖的3dtiles地面遮住就行 names.forEach(name=>{ this.spot.material.uniforms[name].value = depthMatProp[name] }) }else{ let spotSize = this.spot.scale.x * s names.forEach(name=>{ this.spot.material.uniforms[name].value = depthMatProp[name] * spotSize }) } } updatePose( ){ let endPos = this.normal.clone().multiplyScalar(this.lineLength) LineDraw.updateLine(this.line, [new THREE.Vector3(0,0,0), endPos]) this.titleLabel.parent.position.copy(endPos) this.titleLabel.updatePose() viewer.dispatchEvent('content_changed') } changeLineLen(len){ if(len == this.lineLength)return this.lineLength = parseFloat(len) this.updatePose() } dragLineLen(e){ //拖拽线的顶端修改线长度 let endPos = this.normal.clone().multiplyScalar(this.lineLength).applyMatrix4(this.matrixWorld) let normal = this.normal.clone().applyQuaternion(this.getWorldQuaternion(new THREE.Quaternion)) const projected = endPos.clone().project(e.drag.dragViewport.camera); projected.x = e.pointer.x projected.y = e.pointer.y const unprojected = projected.clone().unproject(e.drag.dragViewport.camera); let moveVec = new THREE.Vector3().subVectors(unprojected, endPos); moveVec = moveVec.projectOnVector(normal) let newLength = Math.max(0, this.lineLength + moveVec.dot(normal) ) //console.log(moveVec,newLength) this.changeLineLen(newLength) this.dispatchEvent('dragLineLen') } changePos(info){//注:onMesh时在非平地上拖拽,热点旋转会一直变 this.position.copy(info.position) this.normal.copy(info.normal) let root = this.root this.root = info.root root != this.root && this.updateMatrixWorld() //防止拖动到另一个scale不同的模型上时sprite会缩放闪烁 this.setNorQua() this.updatePose() this.dispatchEvent('posChanged') viewer.dispatchEvent('content_changed') } changeOnMesh(onMesh){//是否贴在mesh上 //if(this.title == 'single2') debugger this.onMesh = onMesh if(onMesh){//贴mesh上时不是sprite,且可设置旋转值 this.add(this.spot) this.titleLabel.position.y = 0 this.setNorQua() this.spot.renderOrder = Potree.config.renderOrders.tag.onMesh.spot // 防止遮住线 this.line.renderOrder = Potree.config.renderOrders.tag.onMesh.line }else{ this.titleLabel.parent.add(this.spot) this.updateTitlePos() this.spot.position.set(0,0,0) this.spot.quaternion.set(0,0,0,1)//this.titleLabel.waitUpdate() this.realFaceAngle = 0 this.spot.renderOrder = Potree.config.renderOrders.tag.spot //还原 this.line.renderOrder = Potree.config.renderOrders.tag.line } Potree.Utils.updateVisible(this.line,'hideTitle', !this.titleLabel.visible && onMesh ? false : true) this.updateDepthParams() viewer.dispatchEvent('content_changed') } setFaceAngle(faceAngle = 0) { //if(this.title == 'single2') debugger this.faceAngle = faceAngle //先记录,但非onMesh时不会用 if(!this.onMesh) return let delta = faceAngle - (this.realFaceAngle || 0) //this.plane.quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(-faceAngle)) this.spot.rotateOnAxis(new THREE.Vector3(0,0,1), THREE.Math.degToRad(delta) ) //this.updateLabelPose() this.realFaceAngle = faceAngle viewer.dispatchEvent('content_changed') } setNorQua() { if(!this.onMesh)return this.spot.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(this.normal, Vectors.ZERO, Vectors.UP)) //重算quaternion this.realFaceAngle = 0 //quaternion被重置了,所以再设置一下faceAngle this.setFaceAngle(this.faceAngle) this.spot.position.copy(this.normal).multiplyScalar(0.01) //在mesh之上偏移一点 } /* 如果要像四维看看那样,在地面上时保持初始转向镜头的话,需要矫正且保存quaternion。且要根据世界normal判断是否在地面, 会随着模型改变, 所以也没法仅保存normal去矫正。 要不然就要直接改变faceAngle */ setTitle(title=''){ this.titleLabel.setText(title) this.setTitleVisi(title instanceof Array || title.trim() != '', 'noText') viewer.dispatchEvent('content_changed') } setTitleVisi(v, reason=''){ Potree.Utils.updateVisible(this.titleLabel, 'hideTitle-'+reason, v) //tag.onMesh && Potree.Utils.updateVisible(tag.line, 'hideTitle-'+reason, v) //line的可见性比较复杂,所以干脆跟随title的,reason不记录那么多 this.onMesh && Potree.Utils.updateVisible(this.line, 'hideTitle', this.titleLabel.visible ) viewer.dispatchEvent('content_changed') } setFontSize(fontsize){ this.titleLabel.fontsize = this.fontsize = fontsize this.titleLabel.updateTexture(); //this.updateTitlePos() viewer.dispatchEvent('content_changed') } changeSpotScale(s){ s *= defaultSpotScale this.spot.scale.set(s,s,s) this.updateTitlePos() viewer.dispatchEvent('content_changed') } updateTitlePos(){ this.onMesh || (this.titleLabel.position.y = titleHeight.uponSpot + this.spot.scale.x / 2) } changeMap(url){ let map = texLoader.load(url,()=>{ viewer.dispatchEvent('content_changed') }) this.spot.material.map = map } dispose(){ this.parent.remove(this); this.titleLabel?.dispose() viewer.dispatchEvent('content_changed') } } export default Tag