Tag.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. import * as THREE from "../../../libs/three.js/build/three.module.js";
  2. import {LineDraw, MeshDraw} from "../utils/DrawUtil.js";
  3. import {TextSprite} from './TextSprite.js'
  4. import Sprite from './Sprite.js'
  5. import DepthBasicMaterial from "../materials/DepthBasicMaterial.js";
  6. import CursorDeal from "../utils/CursorDeal.js";
  7. const depthMatProp = { //为了防止拉远后因放大而一半嵌入墙。
  8. useDepth : true ,
  9. startClipDis : 0.5,
  10. clipDistance : 1,//消失距离
  11. startOcclusDis: 0.5,
  12. occlusionDistance: 0.9,//变为backColor距离
  13. maxOcclusionFactor:0.7,
  14. maxClipFactor:1
  15. }
  16. const planeGeo = new THREE.PlaneBufferGeometry(1,1)
  17. let texLoader = new THREE.TextureLoader()
  18. let lineMat, dragPointMat
  19. const defaultLineLength = 1
  20. const defaultSpotScale = 0.35
  21. const titleHeight = {uponSpot:0.1 }//title底部和spot顶端间隔
  22. const Vectors = {
  23. UP : new THREE.Vector3(0,1,0),
  24. ZERO: new THREE.Vector3()
  25. }
  26. class Tag extends THREE.Shim.FollowRootObject{
  27. constructor(o){
  28. super(o.root)
  29. this.title = o.title
  30. this.fontsize = o.fontsize
  31. this.lineLength = o.lineLength != void 0 ? o.lineLength : defaultLineLength
  32. this.position.copy(o.position)
  33. this.normal = o.normal != void 0 ? o.normal : new THREE.Vector3(0,0, 1)
  34. this.build(o)
  35. this.bindEvent()
  36. this.dragEnable = true
  37. }
  38. set dragEnable(state){
  39. this.lineDragPoint.visible = state
  40. }
  41. get dragEnable(){
  42. return this.lineDragPoint.visible
  43. }
  44. build(o){
  45. lineMat || (lineMat = LineDraw.createFatLineMat(Object.assign({},depthMatProp, {
  46. color: '#ffffff', useDepth :true,
  47. lineWidth: 1
  48. })))
  49. let group = new THREE.Object3D()
  50. this.spot = new THREE.Mesh(planeGeo, new DepthBasicMaterial(Object.assign({},depthMatProp,{
  51. transparent:true,
  52. })))
  53. this.spot.name = 'spot'
  54. this.spot.scale.set(defaultSpotScale,defaultSpotScale,defaultSpotScale)
  55. this.spot.renderOrder = this.spot.pickOrder = Potree.config.renderOrders.tag.spot;
  56. Potree.settings.isOfficial || this.changeMap(Potree.resourcePath+'/textures/spot_default.png')
  57. this.line = LineDraw.createFatLine([], {mat:lineMat})
  58. this.line.name = 'tagLine'
  59. this.line.renderOrder = this.line.pickOrder = Potree.config.renderOrders.tag.line;
  60. this.titleLabel = new TextSprite(Object.assign({},depthMatProp,{
  61. root: group, text:'', sizeInfo:{width2d:150},
  62. textColor:{r:255,g:255,b:255,a:1.0},
  63. backgroundColor:{r:0,g:0,b:0,a:0.7},
  64. borderRadius: 6,
  65. fontsize: this.fontsize || 14, fontWeight:'',//thick
  66. renderOrder : Potree.config.renderOrders.tag.label,
  67. pickOrder: Potree.config.renderOrders.tag.label,
  68. useDepth : true ,
  69. maxLineWidth: 300,
  70. transform2Dpercent:{x:0,y:0.5}, //向上移动一半
  71. textAlign: Potree.settings.isOfficial && 'left'
  72. })) //更新sprite时,实际更新的是root: spot的矩阵
  73. this.setTitle(this.title)
  74. this.updateTitlePos()
  75. group.add(this.titleLabel)
  76. group.add(this.spot)
  77. this.add(group);
  78. this.add(this.line)
  79. viewer.tags.add(this)
  80. if(!dragPointMat){
  81. let map = texLoader.load(Potree.resourcePath+'/textures/whiteCircle.png',()=>{})
  82. dragPointMat = {
  83. default: new THREE.MeshBasicMaterial({
  84. map, transparent:true, color:'#0fe', opacity:0, depthTest:false,
  85. }),
  86. hover: new THREE.MeshBasicMaterial({
  87. map, transparent:true, color:'#0fe', opacity:0.4, depthTest:false,
  88. })
  89. }
  90. }
  91. this.lineDragPoint = new THREE.Mesh(planeGeo, dragPointMat.default) //修改线高度时出现的小圆点
  92. this.lineDragPoint.scale.set(0.15,0.15,0.15);
  93. this.lineDragPoint.name = 'lineDragPoint'
  94. this.lineDragPoint.renderOrder = this.lineDragPoint.pickOrder = Potree.config.renderOrders.tag.spot + 3;
  95. group.add(this.lineDragPoint)
  96. this.updatePose()
  97. }
  98. bindEvent(){
  99. let hoverState = {}, grabbingObject
  100. let setDragPointState = (state)=>{
  101. this.lineDragPoint.material = state ? dragPointMat.hover : dragPointMat.default
  102. this.spot.material.opacity = state ? 0.5 : 1
  103. this.titleLabel.sprite.material.opacity = state ? 0.5 : 1
  104. }
  105. {
  106. //因为只有有intersect时才能拖拽,所以写得比较麻烦
  107. let cursor = {hoverGrab:0, grabbing:0}
  108. let setCursor = (name, action)=>{
  109. let state = action == 'add' ? 1 : 0
  110. if(state != cursor[name]){
  111. cursor[name] = state
  112. viewer.dispatchEvent({
  113. type : "CursorChange", action, name
  114. })
  115. }
  116. }
  117. [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('mousemove',(e)=>{
  118. hoverState[e.target.name] = 1
  119. if(this.dragEnable && (viewer.inputHandler.intersect || hoverState['lineDragPoint'])){//能拖拽时
  120. setCursor('hoverGrab', 'add')
  121. }else{
  122. setCursor('hoverGrab', 'remove')
  123. }
  124. }));
  125. [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('mouseleave',(e)=>{
  126. hoverState[e.target.name] = 0
  127. if(!Object.values(hoverState).some(e=>e)){//都没hover才取消
  128. setCursor('hoverGrab', 'remove')
  129. }
  130. /* if(!hoverState.line && !hoverState.spot && !hoverState.label){
  131. this.dispatchEvent('mouseleave')
  132. } */
  133. }));
  134. [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('drag',(e)=>{
  135. if(this.dragEnable && cursor.grabbing){
  136. if(e.target.name == 'lineDragPoint'){
  137. this.dragLineLen(e)
  138. }else{
  139. let info = viewer.tagTool.getPoseByIntersect(e)
  140. info && this.changePos(info)
  141. }
  142. }
  143. }));
  144. [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('startDragging',(e)=>{
  145. this.dragEnable && (viewer.inputHandler.intersect || e.target.name == 'lineDragPoint') && setCursor('grabbing', 'add')
  146. grabbingObject = e.target.name
  147. grabbingObject == 'lineDragPoint' && setDragPointState(true)
  148. }));
  149. [this.line, this.spot, this.lineDragPoint].forEach(e=>e.addEventListener('drop',(e)=>{
  150. this.dragEnable && setCursor('grabbing', 'remove')
  151. grabbingObject = null
  152. hoverState['lineDragPoint'] || setDragPointState(false)
  153. }));
  154. //拖拽线来移动。虽然理想方式是拟真,拖拽时不改变在线上的位置,使之平移,但仔细想想似乎办不到。因为墙面normal是不固定的,尤其在交界处难以确定。不知鼠标在空中的位置,即使是平行镜头移动也无法满足所有情况。matterport是加了底座,移动也是改变底座中心。
  155. }
  156. {
  157. let mouseover = (e)=>{
  158. this.dispatchEvent('mouseover')
  159. }
  160. let mouseleave = (e)=>{
  161. //if(!hoverState.line && !hoverState.spot && !hoverState.label){
  162. this.dispatchEvent('mouseleave')
  163. //}
  164. }
  165. let click = (e)=>{
  166. this.dispatchEvent('click')
  167. }
  168. this.spot.addEventListener('mouseover',mouseover)
  169. this.spot.addEventListener('mouseleave',mouseleave)
  170. this.titleLabel.addEventListener('mouseover',mouseover)
  171. this.titleLabel.addEventListener('mouseleave',mouseleave)
  172. this.spot.addEventListener('click',click)
  173. this.titleLabel.addEventListener('click',click)
  174. }
  175. this.titleLabel.sprite.addEventListener('spriteUpdated',()=>{
  176. this.updateDepthParams()
  177. })
  178. //-----------set line length
  179. // CursorDeal
  180. this.lineDragPoint.addEventListener('mouseover',(e)=>{
  181. grabbingObject || setDragPointState(true)
  182. })
  183. this.lineDragPoint.addEventListener('mouseleave',(e)=>{
  184. grabbingObject != 'lineDragPoint' && setDragPointState(false)
  185. })
  186. }
  187. updateDepthParams(){//为了避免热点嵌入墙壁,实时根据其大小更新材质系数。 但是在倾斜的角度看由于遮挡距离很大肯定会嵌入的
  188. let s = this.titleLabel.parent.scale.x
  189. let names = ['clipDistance', 'occlusionDistance', 'startClipDis', 'startOcclusDis']
  190. let titleSize = Math.max(this.titleLabel.sprite.scale.x, this.titleLabel.sprite.scale.y) * s
  191. names.forEach(name=>{
  192. this.titleLabel.sprite.material.uniforms[name].value = depthMatProp[name] * titleSize
  193. })
  194. if(this.onMesh){//not sprite,还原。 大概能不被崎岖的3dtiles地面遮住就行
  195. names.forEach(name=>{
  196. this.spot.material.uniforms[name].value = depthMatProp[name]
  197. })
  198. }else{
  199. let spotSize = this.spot.scale.x * s
  200. names.forEach(name=>{
  201. this.spot.material.uniforms[name].value = depthMatProp[name] * spotSize
  202. })
  203. }
  204. }
  205. updatePose( ){
  206. let endPos = this.normal.clone().multiplyScalar(this.lineLength)
  207. LineDraw.updateLine(this.line, [new THREE.Vector3(0,0,0), endPos])
  208. this.titleLabel.parent.position.copy(endPos)
  209. this.titleLabel.updatePose()
  210. viewer.dispatchEvent('content_changed')
  211. }
  212. changeLineLen(len){
  213. if(len == this.lineLength)return
  214. this.lineLength = parseFloat(len)
  215. this.updatePose()
  216. }
  217. dragLineLen(e){ //拖拽线的顶端修改线长度
  218. let endPos = this.normal.clone().multiplyScalar(this.lineLength).applyMatrix4(this.matrixWorld)
  219. let normal = this.normal.clone().applyQuaternion(this.getWorldQuaternion(new THREE.Quaternion))
  220. const projected = endPos.clone().project(e.drag.dragViewport.camera);
  221. projected.x = e.pointer.x
  222. projected.y = e.pointer.y
  223. const unprojected = projected.clone().unproject(e.drag.dragViewport.camera);
  224. let moveVec = new THREE.Vector3().subVectors(unprojected, endPos);
  225. moveVec = moveVec.projectOnVector(normal)
  226. let newLength = Math.max(0, this.lineLength + moveVec.dot(normal) )
  227. //console.log(moveVec,newLength)
  228. this.changeLineLen(newLength)
  229. this.dispatchEvent('dragLineLen')
  230. }
  231. changePos(info){//注:onMesh时在非平地上拖拽,热点旋转会一直变
  232. this.position.copy(info.position)
  233. this.normal.copy(info.normal)
  234. let root = this.root
  235. this.root = info.root
  236. root != this.root && this.updateMatrixWorld() //防止拖动到另一个scale不同的模型上时sprite会缩放闪烁
  237. this.setNorQua()
  238. this.updatePose()
  239. this.dispatchEvent('posChanged')
  240. viewer.dispatchEvent('content_changed')
  241. }
  242. changeOnMesh(onMesh){//是否贴在mesh上
  243. //if(this.title == 'single2') debugger
  244. this.onMesh = onMesh
  245. if(onMesh){//贴mesh上时不是sprite,且可设置旋转值
  246. this.add(this.spot)
  247. this.titleLabel.position.y = 0
  248. this.setNorQua()
  249. this.spot.renderOrder = Potree.config.renderOrders.tag.onMesh.spot // 防止遮住线
  250. this.line.renderOrder = Potree.config.renderOrders.tag.onMesh.line
  251. }else{
  252. this.titleLabel.parent.add(this.spot)
  253. this.updateTitlePos()
  254. this.spot.position.set(0,0,0)
  255. this.spot.quaternion.set(0,0,0,1)//this.titleLabel.waitUpdate()
  256. this.realFaceAngle = 0
  257. this.spot.renderOrder = Potree.config.renderOrders.tag.spot //还原
  258. this.line.renderOrder = Potree.config.renderOrders.tag.line
  259. }
  260. Potree.Utils.updateVisible(this.line,'hideTitle', !this.titleLabel.visible && onMesh ? false : true)
  261. this.updateDepthParams()
  262. viewer.dispatchEvent('content_changed')
  263. }
  264. setFaceAngle(faceAngle = 0) {
  265. //if(this.title == 'single2') debugger
  266. this.faceAngle = faceAngle //先记录,但非onMesh时不会用
  267. if(!this.onMesh) return
  268. let delta = faceAngle - (this.realFaceAngle || 0)
  269. //this.plane.quaternion.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(-faceAngle))
  270. this.spot.rotateOnAxis(new THREE.Vector3(0,0,1), THREE.Math.degToRad(delta) )
  271. //this.updateLabelPose()
  272. this.realFaceAngle = faceAngle
  273. viewer.dispatchEvent('content_changed')
  274. }
  275. setNorQua() {
  276. if(!this.onMesh)return
  277. this.spot.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(this.normal, Vectors.ZERO, Vectors.UP)) //重算quaternion
  278. this.realFaceAngle = 0 //quaternion被重置了,所以再设置一下faceAngle
  279. this.setFaceAngle(this.faceAngle)
  280. this.spot.position.copy(this.normal).multiplyScalar(0.01) //在mesh之上偏移一点
  281. }
  282. /*
  283. 如果要像四维看看那样,在地面上时保持初始转向镜头的话,需要矫正且保存quaternion。且要根据世界normal判断是否在地面, 会随着模型改变, 所以也没法仅保存normal去矫正。
  284. 要不然就要直接改变faceAngle
  285. */
  286. setTitle(title=''){
  287. this.titleLabel.setText(title)
  288. this.setTitleVisi(title instanceof Array || title.trim() != '', 'noText')
  289. viewer.dispatchEvent('content_changed')
  290. }
  291. setTitleVisi(v, reason=''){
  292. Potree.Utils.updateVisible(this.titleLabel, 'hideTitle-'+reason, v)
  293. //tag.onMesh && Potree.Utils.updateVisible(tag.line, 'hideTitle-'+reason, v)
  294. //line的可见性比较复杂,所以干脆跟随title的,reason不记录那么多
  295. this.onMesh && Potree.Utils.updateVisible(this.line, 'hideTitle', this.titleLabel.visible )
  296. viewer.dispatchEvent('content_changed')
  297. }
  298. setFontSize(fontsize){
  299. this.titleLabel.fontsize = this.fontsize = fontsize
  300. this.titleLabel.updateTexture();
  301. //this.updateTitlePos()
  302. viewer.dispatchEvent('content_changed')
  303. }
  304. changeSpotScale(s){
  305. s *= defaultSpotScale
  306. this.spot.scale.set(s,s,s)
  307. this.updateTitlePos()
  308. viewer.dispatchEvent('content_changed')
  309. }
  310. updateTitlePos(){
  311. this.onMesh || (this.titleLabel.position.y = titleHeight.uponSpot + this.spot.scale.x / 2)
  312. }
  313. changeMap(url){
  314. let map = texLoader.load(url,()=>{
  315. viewer.dispatchEvent('content_changed')
  316. })
  317. this.spot.material.map = map
  318. }
  319. dispose(){
  320. this.parent.remove(this);
  321. this.titleLabel?.dispose()
  322. viewer.dispatchEvent('content_changed')
  323. }
  324. }
  325. export default Tag