Hot.js 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178
  1. //合并热点和展览
  2. g_currentHot = null
  3. let texLoader
  4. const playVideoWhenFlyOut = false
  5. //同时可播放的最大个数:
  6. const videoHasSound = false
  7. const playVideoMax = videoHasSound ? 1 :( window.isEdit ? 3 : browser.isMobile() ? 1 : 3);
  8. const playAniMax = window.isEdit ? 6 : browser.isMobile() ? 3 : 5;
  9. const playSyncGroup = [//需要播放同步的视频。 每次都单独定制
  10. // ['okh1UR466371', 'LGmLHP2615503' , 'VNyBI6614896'] //中,左,右 场景SHANGJJ
  11. ]
  12. window.initHot = function(model){
  13. var objLoader = new THREE.OBJLoader()
  14. var _planeGeometry = new THREE.PlaneGeometry(1,1)
  15. var _boxGeometry = new THREE.BoxBufferGeometry(1,1,1)
  16. {//ie的mesh 加了polygonOffset也是会重叠。所以去掉前面的face: (但是突然ie又播放不了videoTexture)
  17. var newIndex = [..._boxGeometry.index.array]
  18. newIndex.splice(4 * 6, 12)
  19. _boxGeometry.setIndex(new THREE.BufferAttribute(new Uint16Array(newIndex),1))
  20. }
  21. var originPhotoCount = photoLoaded = originModelCount = modelLoaded = 0;
  22. var defaultTex1 = Texture.load(g_HotImage.point);
  23. var defaultTex2 = Texture.load(g_HotImage.point2)
  24. dealMap(defaultTex1,{ignoreResize:true} );
  25. dealMap(defaultTex2,{ignoreResize:true} ); //这张图改为linear有黑边。 但即使不改,chrome调试手机版也有黑边和锯齿
  26. /* var _boxMat = new THREE.MeshBasicMaterial({
  27. color: "#eeeeee",
  28. transparent: !0,
  29. opacity: 0.8
  30. }) */
  31. var _boxMat = new THREE.MeshPhongMaterial({
  32. color: "#eeeeee",
  33. transparent: !0,
  34. opacity: 0.8,
  35. side:THREE.DoubleSide
  36. })
  37. var autoSizeInfo = /* {width2d:50}// */{minSize : 120, maxSize : 600, nearBound : 1 , farBound : 15}
  38. var hotGroup = new THREE.Object3D; hotGroup.name = "hotGroup"
  39. model.add(hotGroup); model.hotGroup = hotGroup
  40. var animateTexSrcs = {}
  41. var getLink = function(link){
  42. var src = '';
  43. var r = link.substring(link.indexOf("html") + 4)
  44. , o = "en" == manage.number("lang") ? "&lang=" + manage.number("lang") : "";
  45. -1 == r.indexOf("?") ? src = link + "?time=" + randomTime().getTime() + "&id=" + window.number + o : src = link + "&time=" + randomTime().getTime() + "&id=" + window.number + o
  46. return src
  47. }
  48. var removeSrcPostMark = function(url){//去除texture.load时自动加上的'?'
  49. var index = url.indexOf('?')
  50. if(index>-1){
  51. return url.slice(0, index)
  52. }else return url
  53. }
  54. {//get plane Bound
  55. var planeBound = new THREE.Box3()
  56. var cornerPoint = [
  57. new THREE.Vector3(-0.5, 0.5, 0),
  58. new THREE.Vector3(0.5, 0.5, 0),
  59. new THREE.Vector3(0.5, -0.5, 0),
  60. new THREE.Vector3(-0.5, -0.5, 0),
  61. ]
  62. cornerPoint.forEach(e=>{
  63. planeBound.expandByPoint(e)
  64. })
  65. }
  66. var shineMats = [];
  67. var getShineMat = function(texture1, texture2){
  68. var mat = shineMats.find(e=>e.uniforms.texture1.value == texture1 && e.uniforms.texture2.value == texture2)
  69. if(mat) return mat
  70. else{
  71. var mat = new THREE.ShaderMaterial({
  72. uniforms: {
  73. opac: {
  74. type: "f",
  75. value: 0
  76. },
  77. texture1: {
  78. type: "t",
  79. value: texture1
  80. },
  81. texture2: {
  82. type: "t",
  83. value: texture2
  84. }
  85. },
  86. vertexShader: "varying vec2 vUv;\n\nvoid main() {\n\n vUv = uv ;\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n}\n",
  87. fragmentShader: "varying vec3 vNormal;\n\nvarying vec2 vUv;\n\nuniform float opac;\n\n uniform sampler2D texture1;\n\nuniform sampler2D texture2;\n\nvoid main() {\n\nvec4 tcolor1 = texture2D( texture1, vUv );\n\nvec4 tcolor2 = texture2D( texture2, vUv );\n\ngl_FragColor = mix(tcolor1,tcolor2 ,opac) + tcolor2*0.2; }\n"
  88. ,transparent: !0
  89. })
  90. shineMats.push(mat)
  91. return mat
  92. }
  93. }
  94. var modelGeos = [];
  95. var getModelGeo = function(url){
  96. var geo = modelGeos.find(e=>e.url = url )
  97. if(geo) return geo
  98. else{
  99. var geo = null;
  100. geo.url = url
  101. }
  102. }
  103. const ctlBtns = {
  104. play : Texture.load('images/soundPlay.png') ,
  105. pause: Texture.load('images/soundPause.png')
  106. }
  107. const ctlBtn = new THREE.Mesh(_planeGeometry,new THREE.MeshBasicMaterial({map:ctlBtns.pause, depthTest:false, transparent:true}))
  108. ctlBtn.name = 'ctlBtn'
  109. ctlBtn.renderOrder = 5
  110. window.ctlBtn = ctlBtn
  111. class Hot extends THREE.Object3D{
  112. constructor(info, source){
  113. super()
  114. this.sid = info.sid;
  115. this.preDeal(info, source)// source:旧版来源
  116. this.info = info
  117. this.cornerPoints = []
  118. this.build(info);
  119. this.name = "hot_" + this.sid ;
  120. model.hots[info.sid] = this
  121. }
  122. build(info) {
  123. hotGroup.add(this)
  124. this.setFromInfo(info)
  125. }
  126. setFromInfo(info, media, objObject) {
  127. //1 恢复到编辑之前 2 初始加载
  128. var plane = this.plane;
  129. /* var transformAtPanos = {}
  130. for(var i in info.transformAtPanos){
  131. transformAtPanos[i] = {//只保留一个位移,主要原因是大小变化和热点大小设置冲突了, isSprite和qutaernion衝突
  132. pos : info.transformAtPanos[i].pos && info.transformAtPanos[i].pos.clone(),
  133. //qua : info.transformAtPanos[i].qua && info.transformAtPanos[i].qua.clone(),
  134. }
  135. }
  136. this.transformAtPanos = transformAtPanos */
  137. //2024.3.18恢复transformAtPanos三个属性,isSprite优先级高于quaternion, 全局的热点大小改变不会影响当前有transformAtPanos的大小
  138. //在每个漫游点独立设置的position。
  139. var curPanoTransform = this.info.transformAtPanos[ getTransformSid()] || {}
  140. //没有单独设置position的漫游点使用的position
  141. this.position.copy(curPanoTransform.pos || info.position)
  142. this.quaternion.copy(curPanoTransform.quaternion || info.quaternion)
  143. this.scale.copy(curPanoTransform.scale || info.scale)
  144. this.changeTexType(info.texType, media)
  145. if(!info.objSrc){
  146. this.addPlane()
  147. if (!!this.hasBox != !!info.hasBox) {
  148. this.addBox(!this.hasBox);
  149. }
  150. }else{
  151. this.addModel(objObject || new THREE.Mesh())//暂时创建个空的
  152. }
  153. this.updateMatrixWorld()
  154. /* if(!this.info.visiblePanos){//移到model.build时,在collider建好之后
  155. this.getVisiblePanos()
  156. } */
  157. {//gif
  158. if(this.animation){
  159. GifTexDeal.remove(this.animation)
  160. }
  161. if(this.info.animateInfo && this.material_.map){
  162. this.animation = GifTexDeal.addAnimation(this.material_.map, this, this.info.animateInfo, this.sid )
  163. this.visible && this.inSight() && GifTexDeal.start(this.animation)
  164. }
  165. }
  166. //设置visibles floor------------------
  167. if(window.isEdit)this.setVisiblePanos();
  168. else this.getFloor() //可能需要分批计算
  169. this.setTitleElem()
  170. this.update(player) //when edit done sprite update quaternion
  171. //------------------------------------
  172. /* if(this.info.modelBound){
  173. this.mesh.updateMatrixWorld()
  174. this.mesh.boxHelper2 = new THREE.Box3Helper( new THREE.Box3().copy(this.info.modelBound.bound).applyMatrix4(this.mesh.matrixWorld), new THREE.Color("#00aaee"));
  175. model.add(this.mesh.boxHelper2)
  176. }else{
  177. var bound = new THREE.Box3()
  178. var cornerPoint = [
  179. new THREE.Vector3(-0.5, 0.5, 0),
  180. new THREE.Vector3(0.5, 0.5, 0),
  181. new THREE.Vector3(0.5, -0.5, 0),
  182. new THREE.Vector3(-0.5, -0.5, 0),
  183. ]
  184. cornerPoint.forEach(e=>{
  185. bound.expandByPoint(e)
  186. })
  187. this.mesh.updateMatrixWorld()
  188. this.mesh.boxHelper2 = new THREE.Box3Helper( bound.applyMatrix4(this.mesh.matrixWorld), new THREE.Color("#00aaee"));
  189. model.add(this.mesh.boxHelper2)
  190. } */
  191. }
  192. changeTexType(texType, media){
  193. var plane = this.plane;
  194. if( this.texType != texType || media == 'clear'){
  195. //删除旧的
  196. if(this.texType == 'shine'){
  197. /* this.material_.uniforms.texture1.value.dispose()
  198. this.material_.uniforms.texture2.value.dispose() */ //为了恢复 不删
  199. }else{
  200. //this.material_.map.dispose()
  201. if(this.texType == 'video'){
  202. this.texMedia && this.texMedia.pause()
  203. }else{
  204. }
  205. }
  206. if(this.material_ && !hotGroup.children.find(e=>e!=this && e.material_ == this.material_ )){
  207. this.material_.dispose()
  208. }
  209. //添加新的
  210. if(texType == 'shine'){
  211. }else{
  212. this.changeMaterial(new THREE.MeshBasicMaterial({
  213. color: "#00c8af",
  214. opacity: 0.4,
  215. transparent: !0,
  216. polygonOffset: true,
  217. //是否开启多边形偏移 //ie不开启时blank也不会闪烁
  218. polygonOffsetFactor: -0.9,
  219. //多边形偏移因子
  220. polygonOffsetUnits: -4.0,
  221. //多边形偏移单位
  222. }))
  223. if(texType == 'video'){
  224. }else{
  225. }
  226. }
  227. }
  228. this.texType = texType
  229. if(media == 'clear'){
  230. this.material_.opacity = 0.4;
  231. this.texMedia = null
  232. return
  233. }
  234. if(texType == 'video'){//视频不能共用一个,否则会一起播放暂停
  235. var video
  236. if(media){
  237. video = media
  238. }else{
  239. if(!this.info.texSrc)return
  240. var video = $(`<video controls="controls" loop x5-playsinline="" webkit-playsinline="true" playsinline="true" controlslist="nodownload" preload="meta" ></video>`)[0]
  241. if(window.isEdit) video.src = manage.dealURL(this.info.texSrc); //注意,src赋值就会自动加载, preload="meta" 只加载元数据,提高加载速度,否则一开始卡
  242. /* let src = this.info.texSrc
  243. if(window.isEdit) src = manage.dealURL(src);
  244. var video = window.videoPlayer.getVideo(src)*/
  245. video.name = this.info.fileName
  246. }
  247. video.setAttribute("crossOrigin", 'Anonymous')//要在src设置好前解决跨域
  248. $(video).on('contextmenu', function() {
  249. return false;
  250. });//禁止右键点击出
  251. this.texMedia = video;
  252. video.oncanplaythrough = (e)=> {
  253. if(this.texMedia == video){
  254. /* if(video.paused) *///console.log({str:'oncanplaythrough '+this.sid, level:1})
  255. //this.material_.map.needsUpdate = !0 //当初为何加这句?
  256. //this.update(player)
  257. }
  258. }
  259. /* video.onloadstart = (e)=> {
  260. console.log({str:'onloadstart '+this.sid, level:1})
  261. }
  262. video.onloadedmetadata = (e)=> {
  263. console.log({str:'onloadedmetadata '+this.sid, level:1})
  264. }
  265. video.onloadeddata = (e)=> {
  266. console.log({str:'onloadeddata '+this.sid, level:1})
  267. }
  268. video.oncanplay = (e)=> {
  269. console.log({str:'oncanplay '+this.sid, level:1})
  270. } */
  271. if(!videoHasSound){
  272. video.volume = 0
  273. video.muted = true
  274. }
  275. video.currentTime = 0
  276. if(!window.isEdit) this.material_.opacity = 0.3;
  277. this.material_.map = new THREE.VideoTexture(video)
  278. this.material_.map.wrapS = this.material_.map.wrapT = THREE.ClampToEdgeWrapping;
  279. this.material_.map.generateMipmaps = true;
  280. }else if(texType == 'photo'){
  281. if(media){
  282. this.texMedia = media;
  283. this.info.texSrc = media.src;
  284. this.material_.map = new THREE.Texture(); //texture也不能共用一个,因为有的会有动画,就不一样
  285. this.material_.map.image = media //image可以共用
  286. this.material_.map.needsUpdate = !0
  287. this.material_.opacity = 1
  288. }else{
  289. if(!this.info.texSrc)return
  290. this._loadDones = []
  291. this.material_.opacity = 0.1;
  292. }
  293. }else if(texType == 'shine'){
  294. if(media){
  295. this.changeShineTex(media)
  296. }else{
  297. this.styleImg = /* this.styleImg || */ [];
  298. if(this.info.styleImg){
  299. this.styleImg = this.info.styleImg.map((src)=>{
  300. return {src:/* manage.dealURL( */src } //如果要dealURL,在predeal里
  301. })
  302. }
  303. this.changeShineTex(this.styleImg)
  304. }
  305. this.info.texSrc = null
  306. }
  307. if(this.material_.map){
  308. /* this.material_.map.minFilter = THREE.LinearFilter;
  309. this.material_.map.magFilter = THREE.LinearFilter; */
  310. dealMap(this.material_.map);
  311. this.material_.color.set("#FFFFFF");
  312. this.material_.needsUpdate = true
  313. }
  314. }
  315. changeShineTex(styleImg){
  316. styleImg = styleImg || this.styleImg
  317. var tex1, tex2
  318. if(styleImg.length){
  319. tex1 = Texture.load(styleImg[0].src);
  320. tex2 = Texture.load(styleImg[1]&&styleImg[1].src || styleImg[0].src);
  321. dealMap(tex1,{ignoreResize:true} )
  322. dealMap(tex2,{ignoreResize:true} )
  323. }else{
  324. tex1 = defaultTex1;
  325. tex2 = defaultTex2;
  326. }
  327. this.changeMaterial(getShineMat(tex1, tex2))
  328. this.styleImg = styleImg
  329. this.info.styleImg = styleImg.map(img=>img.src)
  330. }
  331. changeMaterial(mat){
  332. this.material_ = mat;
  333. this.mesh && this.mesh.traverse((mesh)=>{
  334. if(mesh.material && !(mesh instanceof THREE.Box3Helper)){
  335. mesh.material = this.material_;
  336. }
  337. })
  338. }
  339. preDeal(info, source){// source:来源
  340. var convertValue = function(v ,Type){
  341. var value;
  342. if(v instanceof Array){
  343. v.forEach((v1)=>{v1 = parseFloat(v1)})
  344. value = new Type().fromArray(v);
  345. }else{
  346. if(!(v instanceof Type)){
  347. for(let i in v){v[i] = parseFloat(v[i])}
  348. value = new Type().copy(v);
  349. }else{
  350. value = v
  351. }
  352. }
  353. return value
  354. }
  355. if (!info.transformAtPanos) info.transformAtPanos = {}
  356. if(source == 'byHot'){
  357. var infoAttribute = info.infoAttribute || {}
  358. info.title = infoAttribute.title || info.title
  359. info.model = infoAttribute.model || info.model || [] //模型链接
  360. info.images = infoAttribute.images || info.images || []
  361. info.video = infoAttribute.video || info.video || []
  362. info.bgName = infoAttribute.bgName || info.bgName
  363. info.backgroundMusic = info.backgroundMusic || info.backgroundMusic
  364. info.iframe = infoAttribute.iframe || info.iframe || []
  365. info.styleImg = infoAttribute.styleImg || info.styleImg || []
  366. info.content = infoAttribute.content || info.content
  367. {
  368. let action = CloneObject(settings.hotClickEvent.shine);
  369. if(info.actionType == 'noAction' || info.noAction){
  370. action.examine = false,
  371. action.openHot = false
  372. }else if(info.actionType == 'dontExam'){
  373. action.examine = false
  374. }
  375. info.actionType = action
  376. }
  377. if(info.quaternion){
  378. info.quaternion = convertValue(info.quaternion, THREE.Quaternion )
  379. }else{
  380. info.rotation = convertValue(info.rotation, THREE.Vector3 )
  381. info.quaternion = new THREE.Quaternion().setFromEuler( new THREE.Euler().setFromVector3(info.rotation)) //热点的旧数据很多是字符串
  382. }
  383. var s = Hot.getDefaulScale(info.hotIconScale) //旧版的大小,统一转换成新版
  384. info.scale = new THREE.Vector3(s,s,0.02)
  385. delete info.infoAttribute;
  386. /* for (let i in this.transformAtPanos) {
  387. info.transformAtPanos[i].pos = new THREE.Vector3().fromArray(info.transformAtPanos[i].pos)
  388. info.transformAtPanos[i].qua && (info.transformAtPanos[i].qua = new THREE.Quaternion().fromArray(info.transformAtPanos[i].qua))
  389. } */
  390. info.texType = "shine"
  391. }else{
  392. if(source == 'byOverlay'){
  393. info.texType = info.media[0]
  394. //info.title = info.texType == 'video'?'视频':'图片'
  395. info.texSrc = info.file
  396. info.actionType = CloneObject(settings.hotClickEvent[info.texType]);//给一个默认
  397. delete info.media
  398. info.quaternion = convertValue(info.qua, THREE.Quaternion)
  399. info.position = info.pos
  400. delete info.pos;
  401. delete info.qua;
  402. let a = info.texSrc.split('/');
  403. info.fileName = a.pop()
  404. info.scale = new THREE.Vector3(
  405. info.width,
  406. info.height,
  407. info.depth
  408. )
  409. delete info.width; delete info.height; delete info.depth;
  410. delete info.file
  411. }else{
  412. info.quaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler().fromArray(info.rotation))//.setFromVector3(info.rotation)
  413. }
  414. info.model = info.model || [] //模型链接
  415. info.images = info.images || []
  416. info.video = info.video || []
  417. info.iframe = info.iframe || []
  418. info.styleImg = info.styleImg || []
  419. info.imagesDesc = info.imagesDesc || []
  420. info.videosDesc = info.videosDesc || []
  421. info.titleShowType = info.titleShowType || 'hover'
  422. info.titlePos = info.titlePos || 'right'
  423. }
  424. if(info.texSrc){
  425. info.texSrc = manage.removeSrcPostMark(info.texSrc)
  426. }
  427. //whole:
  428. //为了兼容旧数据,尽量和hot的数据靠近,最后保存在hot里
  429. info.position = convertValue(info.position, THREE.Vector3)
  430. info.scale = convertValue(info.scale, THREE.Vector3)
  431. delete info.rotation //暂时不用,只有保存时才存为roration
  432. info.linkType = info.linkType || "common"
  433. for (let i in info.transformAtPanos) {
  434. info.transformAtPanos[i].pos = new THREE.Vector3().fromArray(info.transformAtPanos[i].pos)
  435. info.transformAtPanos[i].qua && (info.transformAtPanos[i].qua = new THREE.Quaternion().fromArray(info.transformAtPanos[i].qua))
  436. info.transformAtPanos[i].scale && (info.transformAtPanos[i].scale = new THREE.Vector3().fromArray(info.transformAtPanos[i].scale))
  437. }
  438. }
  439. addBox(state) {
  440. if (state == !!this.hasBox) {
  441. return;
  442. }
  443. if (state) {
  444. var box = new THREE.Mesh(_boxGeometry,_boxMat)
  445. box.position.set(0, 0, 1 / 2);
  446. box.renderOrder = 3
  447. this.plane.position.set(0, 0, 1);
  448. this.add(box);
  449. this.box = box;
  450. } else {
  451. this.plane.position.set(0, 0, 0);
  452. this.remove(this.box);
  453. this.box = null;
  454. }
  455. this.hasBox = this.info.hasBox = state
  456. }
  457. /* getSizeByScale() {
  458. return {
  459. width: settings.defaultOverlaySize[0] * this.scale.x,
  460. height: settings.defaultOverlaySize[1] * this.scale.y
  461. }
  462. }
  463. getScaleBySize(width, height) {
  464. return {
  465. x: width / settings.defaultOverlaySize[0],
  466. y: height / settings.defaultOverlaySize[1],
  467. }
  468. }*/
  469. setVisiblePanos(visibleData){
  470. if(visibleData){
  471. this.info.visiblePanos = visibleData
  472. }else if(!this.info.visiblePanos){
  473. this.getVisiblePanos()
  474. }
  475. this.getFloor()
  476. }
  477. getFloor(){//用于飞出后分层展示。最好只属于一个楼层,且是贴得最近的
  478. //先直接向下探测
  479. //不准的话再向四周选三个方向,以及visiblePanos,来加成求分
  480. if(player.model.floors.list.length <= 1)return
  481. if(this.floorByRay){
  482. return this.floor = this.floorByRay //重复getFloor的话,因模型不会变,用ray得到的floor不会变
  483. }
  484. let result
  485. if(this.floorByRay !== false){
  486. let map = new Map
  487. let downVec = new THREE.Vector3(0,-1,0)
  488. let pos = this.info.transformAtPanos.outSide?.pos || this.position
  489. let centerPos
  490. if(!this.plane){
  491. let bound = new THREE.Box3().copy(this.info.modelBound.bound)
  492. let center = bound.center()
  493. let qua = this.info.transformAtPanos.outSide?.qua || this.quaternion
  494. let scale = this.info.transformAtPanos.outSide?.scale || this.scale
  495. var matrixWorld = new THREE.Matrix4().compose(pos, qua, scale)
  496. matrixWorld.multiplyMatrices(matrixWorld, this.mesh.matrix)
  497. centerPos = center.applyMatrix4(matrixWorld);
  498. }else{
  499. centerPos = pos
  500. }
  501. let request = [(floor)=>{
  502. player.raycaster.set(centerPos, downVec)
  503. let n = player.raycaster.intersectObjects(floor.collider.children , true );
  504. if(n && n[0]){
  505. map.set(floor, n[0].distance)
  506. return true
  507. }
  508. }]
  509. let rank = [(floor)=>{
  510. return - map.get(floor)
  511. }]
  512. result = common.sortByScore(player.model.floors.list, request, rank)
  513. }
  514. if(result){
  515. this.floor = this.floorByRay = result[0].item
  516. }else{
  517. this.floorByRay = false //标记 使用ray无法找到
  518. console.warn('热点通过raycaster没有找到楼层', this)
  519. if(this.info.visiblePanos){
  520. let floorIndex = this.info.visiblePanos.map(e=>player.model.panos.index[e].floorIndex).sort()[Math.floor(this.info.visiblePanos.length/2)]//中位数
  521. this.floor = player.model.floors.index[floorIndex]
  522. console.log('热点根据visiblePanos找到楼层', this, this.floor.name)
  523. }else{
  524. this.floor = null
  525. }
  526. }
  527. }
  528. getVisiblePanos(){//在不同点还不一样
  529. var depth = this.hasBox ? this.scale.z : 0;
  530. var width = this.scale.x, height = this.scale.y
  531. var cornerPoint
  532. if(this.plane){
  533. cornerPoint = [
  534. new THREE.Vector3(0, 0, depth),
  535. new THREE.Vector3(-width/2, height/2, depth),
  536. new THREE.Vector3(width/2, height/2, depth),
  537. new THREE.Vector3(width/2, -height/2, depth),
  538. new THREE.Vector3(-width/2, -height/2, depth),
  539. ]
  540. }else{
  541. var bound = new THREE.Box3().copy(this.info.modelBound.bound)
  542. var center = bound.center()
  543. cornerPoint = [
  544. new THREE.Vector3(center.x,center.y,center.z),
  545. new THREE.Vector3(bound.min.x, bound.min.y, bound.min.z ),
  546. new THREE.Vector3(bound.min.x, bound.min.y, bound.max.z ),
  547. new THREE.Vector3(bound.min.x, bound.max.y, bound.min.z ),
  548. new THREE.Vector3(bound.max.x, bound.min.y, bound.min.z ),
  549. new THREE.Vector3(bound.max.x, bound.max.y, bound.min.z ),
  550. new THREE.Vector3(bound.max.x, bound.min.y, bound.max.z ),
  551. new THREE.Vector3(bound.min.x, bound.max.y, bound.max.z ),
  552. new THREE.Vector3(bound.max.x, bound.max.y, bound.max.z ),
  553. ];
  554. }
  555. var getPos = (position, quaternion=this.info.quaternion, scale=this.info.scale)=>{//每个overlay位置对应5个坐标,plane中心和四个角的位置
  556. if(this.plane){
  557. return cornerPoint.map(e=>{
  558. return e.clone().applyQuaternion(this.info.quaternion).add(position)
  559. })
  560. }else{
  561. var matrixWorld = new THREE.Matrix4().compose(position, quaternion, scale)
  562. matrixWorld.multiplyMatrices(matrixWorld, this.mesh.matrix)
  563. return cornerPoint.map(e=>{
  564. return e.clone().applyMatrix4(matrixWorld);
  565. })
  566. }
  567. }
  568. this.info.visiblePanos = []
  569. var customPositions = getPos(this.info.position )
  570. var posAtPanos = {}
  571. for(let panoId in this.info.transformAtPanos){
  572. if(panoId == 'outSide')continue;
  573. let {pos,qua,scale} = this.info.transformAtPanos[panoId]
  574. posAtPanos[panoId] = getPos(pos,qua,scale)
  575. }
  576. let maxCount = browser.isMobile() ? 2000 : 5000
  577. let possiblePanos = model.panos.list
  578. if(this.plane && !this.info.isSprite){//目前都是单面,所以只要可以看到的一面的热点
  579. let defaultDir = new THREE.Vector3(0,0,-1).applyQuaternion(this.info.quaternion).negate()
  580. possiblePanos = possiblePanos.filter(pano=>{
  581. let dir1
  582. if(this.info.transformAtPanos[pano.id]?.qua){
  583. dir1 = new THREE.Vector3(0,0,-1).applyQuaternion(this.info.transformAtPanos[pano.id].qua).negate()
  584. }else{
  585. dir1 = defaultDir
  586. }
  587. let dir2 = new THREE.Vector3().subVectors(pano.position, this.position).normalize();
  588. return dir1.dot(dir2)>0
  589. })
  590. }
  591. let c = model.panos.list.length * model.colliders.length
  592. if(window.isEdit || c < maxCount){ //编辑页面保险起见还是全部算完后才可浏览,就能保证保存全部的visiblePano
  593. this.info.visiblePanos = common.getVisiblePano(customPositions, possiblePanos , {
  594. model: model.colliders , posAtPanos
  595. })
  596. }else{
  597. let start = 0
  598. let interval = setInterval(()=>{
  599. let end = start + Hot.visiPanosCountSlice;
  600. end = Math.min(end, possiblePanos.length)
  601. let i = start
  602. start = end
  603. let panos = possiblePanos.slice(i,end)
  604. this.info.visiblePanos = this.info.visiblePanos.concat( common.getVisiblePano(customPositions, panos, {
  605. model: model.colliders , posAtPanos
  606. }));
  607. if(end>=possiblePanos.length){
  608. //console.log(window.hotsi ?(++window.hotsi): (window.hotsi = 1))
  609. this.getFloor() //re get
  610. clearInterval(interval)
  611. }
  612. }, Hot.visiEveryDurSlice )// visiEveryDurSlice 等在main中定义
  613. }
  614. }
  615. updateVisible(panos, visibility, type) {
  616. if(window.isEdit && editTool.hotpoint.editSpot == this || player.mode != 'panorama' && visibility !== false){
  617. return convertTool.updateVisible(this,'visi',true)
  618. }
  619. let visible = visibility != void 0 ? visibility : (!this.info.visiblePanos ||
  620. (type == 'every' ? panos.every(pano=>this.info.visiblePanos.includes(pano.id)) : panos.some(pano=>this.info.visiblePanos.includes(pano.id))))//type为every时,需要panos中每个都可见才显示
  621. convertTool.updateVisible(this,'visi',visible)
  622. this.titleElem && this.titleElem.setVisible(this.visible, 'hotVisible', 1)
  623. if (this.texType == 'video'){
  624. //this.switchPlay(this.visible, this.visible ? null : 'stop' );//可见时不操作;不可见时停止
  625. this.update(player)
  626. }
  627. }
  628. getWorldCenter(){
  629. return this.getBoundOri().center().applyMatrix4(this.mesh.matrixWorld)
  630. }
  631. getBoundOri(){
  632. let bound
  633. if(this.objObject){
  634. bound = new THREE.Box3().copy(this.info.modelBound.bound)
  635. }else{
  636. bound = planeBound.clone()
  637. }
  638. return bound
  639. }
  640. getCornerPoint(){//获取在每个漫游点上的视觉边界点 可以打开boxHelper和addBall来观测是否准确
  641. if(this.cornerPoints[player.currentPano.id]){
  642. return this.cornerPoints[player.currentPano.id]
  643. }else{
  644. var boundPoint, cornerPoint
  645. var center//中心点
  646. if(this.plane){
  647. center = this.plane.getWorldPosition()
  648. boundPoint = [
  649. new THREE.Vector3(-0.5, 0.5, 0),
  650. new THREE.Vector3(0.5, 0.5, 0),
  651. new THREE.Vector3(0.5, -0.5, 0),
  652. new THREE.Vector3(-0.5, -0.5, 0),
  653. ]
  654. }else{
  655. var bound = new THREE.Box3().copy(this.info.modelBound.bound)
  656. boundPoint = [
  657. new THREE.Vector3(bound.min.x, bound.min.y, bound.min.z ),
  658. new THREE.Vector3(bound.min.x, bound.min.y, bound.max.z ),
  659. new THREE.Vector3(bound.min.x, bound.max.y, bound.min.z ),
  660. new THREE.Vector3(bound.max.x, bound.min.y, bound.min.z ),
  661. new THREE.Vector3(bound.max.x, bound.max.y, bound.min.z ),
  662. new THREE.Vector3(bound.max.x, bound.min.y, bound.max.z ),
  663. new THREE.Vector3(bound.min.x, bound.max.y, bound.max.z ),
  664. new THREE.Vector3(bound.max.x, bound.max.y, bound.max.z ),
  665. ];
  666. }
  667. var maxLon = -Infinity
  668. var minLon = +Infinity
  669. var maxLat = -Infinity
  670. var minLat = +Infinity
  671. var pos1 = player.currentPano.position.clone();
  672. center = this.position.clone() //模型bound的中心点已经位移到了hot中心点。 注意不能用getWorldPosition,得到的会是偏移的
  673. var dir = center.clone().sub(pos1).normalize()
  674. var centerDirInfo = { }
  675. player.cameraControls.controls.panorama.lookAt.call( centerDirInfo , null, dir )
  676. boundPoint.forEach(e=>{//lon左右
  677. var point = e.applyMatrix4(this.mesh.matrixWorld);
  678. var dir = point.clone().sub(pos1).normalize()
  679. var dirInfo = { }
  680. player.cameraControls.controls.panorama.lookAt.call( dirInfo , null, dir )
  681. var diffLon = (dirInfo.lon-centerDirInfo.lon) % 360
  682. if(Math.abs(diffLon)>180){//因为有时需要根据符号判断是在中心的左边还是右边,所以限制在180内
  683. diffLon += (diffLon>0 ? -360 : 360)
  684. }
  685. var diffLat = dirInfo.lat-centerDirInfo.lat
  686. maxLon = Math.max(diffLon, maxLon)
  687. minLon = Math.min(diffLon, minLon)
  688. maxLat = Math.max(diffLat, maxLat)
  689. minLat = Math.min(diffLat, minLat)
  690. })
  691. var diffLon = maxLon - minLon
  692. var diffLat = maxLat - minLat
  693. if(diffLat>180) {//可能是到了反面。不好算,直接返回所有boundPoint
  694. cornerPoint = boundPoint
  695. }else{
  696. //读取lon lat的最大最小值,勾勒出一个没有倾斜的矩形 。它比boundPoint看起来范围更大些
  697. maxLon = maxLon + centerDirInfo.lon
  698. maxLat = maxLat + centerDirInfo.lat
  699. minLon = minLon + centerDirInfo.lon
  700. minLat = minLat + centerDirInfo.lat
  701. var dirs = [
  702. math.getDirByLonLat(maxLon, maxLat),
  703. math.getDirByLonLat(minLon, minLat),
  704. math.getDirByLonLat(maxLon, minLat),
  705. math.getDirByLonLat(minLon, maxLat),
  706. ]
  707. cornerPoint = dirs.map(e=>{
  708. return e.clone().add(pos1)
  709. })
  710. cornerPoint = [center, ...cornerPoint] //最后增加一个中心点
  711. }
  712. /* if(this.objObject){
  713. cornerPoint = [pos2, ...cornerPoint]
  714. } */
  715. //addPoints(cornerPoint)
  716. this.cornerPoints[player.currentPano.id] = {cornerPoint, diffLon, diffLat}
  717. return this.cornerPoints[player.currentPano.id]
  718. }
  719. }
  720. getMediaSize(){
  721. let size = new THREE.Vector2;
  722. if(this.texMedia){
  723. if(this.texType == 'photo'){
  724. size.x = this.texMedia.width;
  725. size.y = this.texMedia.height;
  726. //动画的话再变
  727. if(this.info.animateInfo){
  728. size.x /= this.info.animateInfo.cellXcount
  729. size.y /= this.info.animateInfo.cellYcount
  730. }
  731. }else{
  732. size.x = this.texMedia.videoWidth || this.videoWidth
  733. size.y = this.texMedia.videoHeight || this.videoHeight
  734. }
  735. }
  736. return size
  737. }
  738. inSight(){
  739. //return true
  740. if(window.isEdit)return true // 太容易move了
  741. /* if(overlay.plane.material.side!=2){ //side为0
  742. let dir = overlay.plane.getWorldDirection(new THREE.Vector3)
  743. if(dir.dot(camDir)>0 ){
  744. console.log('dir.dot(camDir)',dir.dot(camDir))
  745. return false
  746. }
  747. //可能看到背面。若能看到正面,视线方向和plane朝向必定为钝角
  748. } */
  749. if(player.mode == 'panorama' && player.currentPano){
  750. if(!player.camera) return
  751. var cornerPointInfo = this.getCornerPoint();
  752. var cornerPoint
  753. let min = new THREE.Vector2(5,5)
  754. let scaleRatio = 1/player.zoomLevel;
  755. /*
  756. //根据media原始大小来调整阈值: media的原始大小能代表期望显示的大小,如果显示大小的远小于期望大小,就不显示(此时能感受到贴图锯齿严重,清晰度被浪费)。比如如果gif是一个很小的按钮,即使diffLon很小也要显示。缺点:需要用户根据所需上传合适清晰度的图。
  757. let size = this.getMediaSize()
  758. if(size.x>0){
  759. scaleRatio *= Math.sqrt(size.x * size.y) / 1000
  760. }
  761. min.multiplyScalar(scaleRatio)
  762. 2025.2:不行,这样大的视频贴上去很容易返回false
  763. */
  764. //gif如果是一个按钮,需要更小的lon . 最好是能有滑块调节可视
  765. if(this.animation){
  766. min.multiplyScalar(0.5)
  767. }
  768. if(cornerPointInfo.diffLon < min.x || cornerPointInfo.diffLat < min.y ){
  769. //console.log('two far and small')
  770. return false
  771. }
  772. if(cornerPointInfo.diffLon < 15 && cornerPointInfo.diffLat < 15){//当很小的时候,只判断中心点即可
  773. cornerPoint = [cornerPointInfo.cornerPoint[0]];
  774. }else{
  775. cornerPoint = cornerPointInfo.cornerPoint
  776. }
  777. for(let i=0,j=cornerPoint.length;i<j;i++){//只要有一点可见就算看见
  778. var pos2d = math.getPos2d(cornerPoint[i], player.camera, $("#player")[0])
  779. if(pos2d.trueSide && pos2d.inSight) return true
  780. }
  781. }else{//飞出 只判断在不在画面内
  782. //return playVideoWhenFlyOut//true
  783. let frustumMatrix = new THREE.Matrix4
  784. frustumMatrix.multiplyMatrices(player.camera.projectionMatrix, player.camera.matrixWorldInverse)
  785. let frustum = new THREE.Frustum();
  786. frustum.setFromMatrix(frustumMatrix)
  787. let bound = this.getBoundOri()
  788. bound.applyMatrix4(this.matrixWorld)
  789. let insight = frustum.intersectsBox(bound)
  790. if(!insight)return false
  791. const min = 0.15//最小可播放尺寸(屏幕宽高都是2)
  792. bound.applyMatrix4(frustumMatrix); //project on screen
  793. let boundSize = bound.size(new THREE.Vector3)
  794. if(boundSize.x < min && boundSize.y < min){
  795. return false
  796. }
  797. return true
  798. }
  799. }
  800. update(player){
  801. if(this.info.isSprite){
  802. this.quaternion.copy(player.camera.quaternion)
  803. }
  804. this.updateScale()
  805. this.updateTitle()
  806. }
  807. updateScale(e, t) {//自适应调节大小
  808. if(!DATA.autoAdjustHotScale || this.texType != 'shine' || !this.plane )return
  809. var scale = convertTool.getScaleForConstantSize($.extend({},autoSizeInfo,{
  810. position: this.position.clone()
  811. }))
  812. this.plane.scale.set(scale,scale,scale) //修改mesh,和自定义修改的scale不冲突
  813. }
  814. switchPlay(state){//手动播放暂停
  815. this.pausedByUser = !state
  816. this.videoControl(state)
  817. }
  818. loadVideo(video){
  819. if(video.src_)return
  820. video.src = video.src_ = manage.dealURL(this.info.texSrc);
  821. }
  822. videoControl(state){
  823. if(this.texType != "video" || !this.material_.map)return
  824. var video = this.texMedia
  825. this.shouldPlay = state
  826. if(!state || state == 'stop'){
  827. if(!video.paused){
  828. video.pause()
  829. if(!video.muted){
  830. SoundManager.pause('hot', true)//自动播放被中断的音频 (bgm
  831. }
  832. console.log({str: "paused " + this.sid, level:1})
  833. }
  834. /* video.lastCurTime = state == 'stop' ? 0 : video.currentTime //记录
  835. video.src = video.src_ = '' */
  836. if(state == 'stop'){
  837. video.currentTime = 0;
  838. }
  839. if(this.sid == 'LezWqUp088015'){
  840. ctlBtn.material.map = ctlBtns.play
  841. }
  842. }else if(state){
  843. if(/* !isVideoPlayed(video) */ video.paused ){
  844. console.log({str: 'videoControl play ' +", "+ this.sid, level:1})
  845. this.loadVideo(video)
  846. video.play()
  847. if(!video.muted){
  848. SoundManager.play('hot') //暂停bgm等
  849. }
  850. //video.currentTime = video.lastCurTime || 0
  851. this.changeOpaWhenPlay(video)
  852. if(this.sid == 'LezWqUp088015'){
  853. ctlBtn.material.map = ctlBtns.pause
  854. }
  855. //处理同步播放
  856. let group = playSyncGroup.find(e=>e.includes(this.sid))
  857. if(group){
  858. let others = group.filter(e=>e != this.sid).map(e=>player.model.hots[e].texMedia)
  859. //console.log('controlVideo play', this.id, 'other currentTime', others.map(e=>e.currentTime))
  860. video.currentTime = others[0].currentTime
  861. }
  862. //if(isVideoPlayed(video))console.log({str:"played " + this.sid + video.duration ,level:1})
  863. }
  864. }
  865. }
  866. changeOpaWhenPlay(video){//当播放成功后,恢复为不透明
  867. if(isNaN(video.duration)){//未加载好
  868. return setTimeout(()=>{
  869. this.changeOpaWhenPlay(video)
  870. },100)
  871. }
  872. const minPlayedTime = Math.min(video.duration/10, 0.1); //到这个时间说明加载成功
  873. if(!video.hasInitedOpacity){
  874. setTimeout(e=>{
  875. //console.log('trychangeOpa ',this.sid,video.currentTime)
  876. if(video.currentTime>minPlayedTime){
  877. this.material_.opacity = 1
  878. video.hasInitedOpacity = true
  879. //console.log('changeOpaWhenPlay',this.sid)
  880. }else{
  881. if(!video.paused){//再次尝试
  882. return setTimeout(()=>{
  883. this.changeOpaWhenPlay(video)
  884. },500)
  885. }
  886. }
  887. },minPlayedTime*1000+500)
  888. }
  889. }
  890. /* setDefaultHotScale = function(){//设置成默认热点大小
  891. var w = DATA.hotIconScale) * g_HotMeshSize.g_HotMeshWidth
  892. this.scale.set( w, w, this.scale.z)
  893. } */
  894. setTitleElem(){
  895. var title = this.info.title;
  896. if(title){
  897. if(!this.titleElem){
  898. this.titleElem = new Label2D({position:this.position, innerHTML:`<div>${title}</div>`, domElement:$("#hot")[0], shelterByModel:true })
  899. }else{
  900. this.titleElem.elem.html(`<div>${title}</div>`)
  901. }
  902. this.titleElem.setVisible(this.visible, 'hotVisible')
  903. this.setHoverState(false)
  904. this.setTitleDir()
  905. }else{
  906. if(this.titleElem){
  907. this.titleElem.dispose()
  908. this.titleElem = null;
  909. }
  910. }
  911. }
  912. setTitleDir(){
  913. this.titleElem && this.titleElem.elem.attr('pos',this.info.titlePos)
  914. }
  915. updateTitle(){
  916. this.titleElem && this.titleElem.update()
  917. }
  918. setHoverState(state){
  919. if(!this.titleElem)return
  920. this.hovered = !!state
  921. var v = this.info.titleShowType != 'unvisible' && (this.info.titleShowType == 'always' || this.hovered)
  922. this.titleElem.setVisible(v, 'hoveredVisi', 1)
  923. //this.updateTitle()
  924. if(this.sid == 'LezWqUp088015'){
  925. if(state){
  926. this.add(ctlBtn) //显示按钮
  927. let scale = 0.8
  928. ctlBtn.scale.set(scale/this.scale.x,scale/this.scale.y,scale/this.scale.z)
  929. ctlBtn.rotation.set(0,Math.PI/2,Math.PI/2)
  930. ctlBtn.position.x = -0.15
  931. }else{
  932. this.remove(ctlBtn)
  933. }
  934. }
  935. }
  936. closestPanoTowardTag(e ) {
  937. var i = []
  938. , n = []
  939. , r = this.getWorldCenter()// this.mesh.getWorldPosition();
  940. if (e === "panorama") {
  941. /* var o = t.position.clone().sub(r).normalize();
  942. n.push(function(t, i) {//scoreFunctions.direction 最好这个漫游点在currentPano到热点之间的路径上。但是这样的话可能就看不到热点正面,所以删掉
  943. return function(e) {
  944. return e.position.clone().sub(t).normalize().dot(i) * window._settings.navigation.directionFactor
  945. }}(r, o)
  946. ) */
  947. }
  948. var a = new THREE.Vector3;
  949. i.push(function(e) {
  950. return Math.abs(e.position.x - r.x) > window._settings.tags.visibility.cameraClearance || Math.abs(e.position.z - r.z) > window._settings.tags.visibility.cameraClearance
  951. },
  952. function(e) {
  953. a.copy(r).sub(e.position);
  954. var t = -THREE.Math.radToDeg(Math.atan(a.y / Math.sqrt(a.x * a.x + a.z * a.z)))
  955. , i = window._settings.tags.navigate.tiltTolerance
  956. return window._settings.insideLookLimitDown - i < t && t < window._settings.insideLookLimitUp + i
  957. },
  958. (pano)=>{ // add
  959. return player.checkHasNeighbor(pano)
  960. },
  961. (pano)=>{ // add
  962. // 周恩光改 解决visiblePanos为undefined时的bug
  963. return !this.info.visiblePanos || this.info.visiblePanos.includes(pano.id)
  964. }
  965. )
  966. n.push(
  967. (function(hot, i) { //scoreFunctions.distanceSquared
  968. return function(pano) {
  969. //i = a.navigation.distanceFactor
  970. return hot ? hot.position.distanceToSquared(pano.position) * i : 0
  971. }
  972. })(this, -2)
  973. ,
  974. (pano)=>{//尽量正对hot
  975. let dir = new THREE.Vector3(0,0,1).applyQuaternion(this.quaternion)
  976. let dir2 = new THREE.Vector3().subVectors(pano.position, this.position).normalize();
  977. let s = dir.dot(dir2) * 60;
  978. //console.log(pano.id + ":" + s)
  979. return s
  980. }
  981. );
  982. /* var tanD = player.domElement.clientWidth / player.domElement.clientHeight * Math.tan((THREE.Math.degToRad(player.zoomFov/2))) ;
  983. var goodDistance = overlay.width / 2 / tanD;
  984. n.push(
  985. function (pano) { //寻找正对着overlay的pano
  986. var overlayDir = new THREE.Vector3(0,0,1).applyQuaternion(overlay.quaternion)
  987. var v1 = overlayDir//.setY(0); 朝上的话set0会得到0,0,0 无法求angle
  988. var v2 = pano.position.clone().sub(overlay.position)//.setY(0)
  989. var angle = v1.angleTo(v2)
  990. angles[pano.id] = angle;
  991. angle = -angle * 100
  992. return angle
  993. },
  994. function (pano) { //寻找正对着overlay的pano
  995. var dis = pano.position.clone().distanceToSquared(overlay.position)
  996. var goodDistance2 = goodDistance * Math.abs(Math.cos(angles[pano.id]))//考虑了倾斜角度后的最佳距离
  997. var result = - 300 * Math.abs(dis-goodDistance2) / goodDistance2 //dis和goodDistance2差距越大分数越低
  998. return result;
  999. }
  1000. ) */
  1001. var s = player.model.panos.sortByScore(i, n);
  1002. //console.log(s)
  1003. return s && 0 < s.length && s[0].item
  1004. }
  1005. examine(options={}) {
  1006. var openHot = this.info.link && this.info.actionType.openHot && !options.dontOpen
  1007. var fastTran = !options.dontFastTran && ( this.info.actionType.fastTran || settings.hotFastTran || options.fastTran)
  1008. if(fastTran){//瞬间过渡 1到固定方位 2到和普通过渡一样的位置,也就是最适合的位置
  1009. let info = this.info.cameraData
  1010. if(info){
  1011. player.blackToPano({
  1012. pano: player.model.panos.index[info.pano.uuid],
  1013. quaternion: new THREE.Quaternion().fromArray(info.camera.quaternion)
  1014. })
  1015. }else{
  1016. let pano = this.closestPanoTowardTag(player.mode ) || player.currentPano,
  1017. pos = this.getWorldCenter(),
  1018. m = (new THREE.Matrix4).lookAt(pano.position, pos, new THREE.Vector3(0,1,0)),
  1019. quaternion = new THREE.Quaternion().setFromRotationMatrix(m)
  1020. player.blackToPano({pano,quaternion})
  1021. }
  1022. }
  1023. var needExamine = !fastTran && (options.examine || (!settings.dontExamHot && this.info.actionType.examine))
  1024. if(!openHot && !needExamine)return;
  1025. if(openHot && this.info.linkType!="common" && this.info.iframe && this.info.iframe[0]){
  1026. var src = getLink(this.info.iframe[0]);
  1027. if(this.info.linkType=="jumpLink"){
  1028. var newPage = window.open(src, "_blank" );
  1029. newPage.focus();
  1030. }else if(this.info.linkType=="iframeDiv"){
  1031. var div = document.createElement("div");
  1032. div.style.position = 'fixed';
  1033. div.style.width = div.style.height = "100%";
  1034. div.style.left = div.style.top = '0';
  1035. div.style["z-index"] = "999"
  1036. var exit = document.createElement("div");
  1037. exit.style["background-image"] = "url(images/vrOffImg.png)";
  1038. exit.style.position = 'absolute';
  1039. exit.style.width = exit.style.height = "50px";
  1040. exit.style.left = '17px'; exit.style.top = "20px"
  1041. exit.style.cursor = "pointer";
  1042. exit.style["background-repeat"] = "no-repeat";
  1043. exit.style["background-size"] = "25%";
  1044. exit.style["background-position"] = "center center";
  1045. exit.style["background-color"] = "rgba(0, 0, 0, 0.2)";
  1046. exit.style["border-radius"] = "50%";
  1047. exit.style["z-index"] = "3"
  1048. exit.onclick = ()=>{
  1049. $(div).remove()
  1050. Hot.closePopup()
  1051. }
  1052. var myElement = document.createElement("iframe");
  1053. myElement.style.position = 'absolute';
  1054. myElement.style.width = myElement.style.height = "100%";
  1055. myElement.style.left = myElement.style.top = '0';
  1056. myElement.src = src
  1057. $("body").append(div);
  1058. div.appendChild(exit);
  1059. div.appendChild(myElement)
  1060. SoundManager.play('hot')
  1061. }
  1062. return;
  1063. }
  1064. if(!player.currentPano)return;
  1065. var popup = document.getElementById("popup");
  1066. if (openHot) {
  1067. g_currentHot = this,
  1068. popup.style.display = "block",
  1069. popup.classList.add("wait");
  1070. var n = document.createElement("iframe");
  1071. SoundManager.play('hot')
  1072. var src = getLink(this.info.link )
  1073. n.src = src;
  1074. n.id = "id1",
  1075. n.allowTransparency = "true";
  1076. var a = document.getElementById("id1");
  1077. if (void 0 === a || null == a) {
  1078. document.querySelector(".popup-content").appendChild(n);
  1079. var s = !1;
  1080. window.loaddingSuccess = function() {
  1081. s = !0
  1082. }
  1083. ,
  1084. setTimeout(function e() {
  1085. if (s) {
  1086. var t = document.querySelector("#id1").contentWindow.document;
  1087. t.querySelector("video") && (t.querySelector("video").play(),
  1088. !t.querySelector("video").paused && t.querySelector(".playPause") && t.querySelector(".playPause").classList.add("fa-pause")),
  1089. t.querySelector("audio") && t.querySelector("audio").play()
  1090. } else
  1091. setTimeout(e, 300)
  1092. }, 800)
  1093. }
  1094. }
  1095. var done = function() {
  1096. player.flyingToTag = !1;
  1097. openHot && popup.classList.remove("wait")
  1098. }.bind(this);
  1099. if(!needExamine){
  1100. done()
  1101. return;
  1102. }
  1103. var c = this.closestPanoTowardTag(player.mode ) || player.currentPano
  1104. , h = this.getWorldCenter()//this.mesh.getWorldPosition();
  1105. player.flyingToTag = !0;
  1106. if (player.mode === 'panorama') {
  1107. var d = {
  1108. pano: c,
  1109. lookAtPoint: h,
  1110. duration: options.duration,
  1111. maxDistanceOverride: null,
  1112. skipWarpingCheck: !1,
  1113. aimDuration: options.aimDuration,
  1114. };
  1115. if(fastTran){
  1116. let f = (new THREE.Matrix4).lookAt(c.position, h, new THREE.Vector3(0,1,0));
  1117. let quaternion = (new THREE.Quaternion).setFromRotationMatrix(f)
  1118. player.blackToPano({
  1119. pano:c,
  1120. quaternion,
  1121. })
  1122. done()
  1123. }else{
  1124. player.flyToPano(d, done)
  1125. }
  1126. } else {
  1127. var p = {
  1128. pano: c
  1129. };
  1130. if (h) {
  1131. var f = (new THREE.Matrix4).lookAt(c.position, h, new THREE.Vector3(0,1,0));
  1132. p.quaternion = (new THREE.Quaternion).setFromRotationMatrix(f)
  1133. }
  1134. p.callback = done,
  1135. p.duration = options.duration || 1500,
  1136. p.mode = 'panorama',
  1137. p.aimDuration = options.aimDuration
  1138. player.flyToNewMode(p)
  1139. }
  1140. }
  1141. addModel(object){
  1142. if(this.objObject){
  1143. this.remove(this.objObject)
  1144. }
  1145. this.objObject = object;
  1146. /* object.traverse((mesh)=>{
  1147. if(mesh.material && mesh.type == "hotSprite"){
  1148. mesh.material = this.material_;
  1149. }
  1150. }) */
  1151. object.name = this.info.objName;
  1152. object.src = this.info.objSrc
  1153. this.info.hasBox = false
  1154. this.addBox(false)
  1155. this.remove(this.plane);
  1156. this.plane = null;
  1157. this.setMesh(this.objObject)
  1158. //this.adjustModelAuto()
  1159. if(this.info.modelBound){//应该不会改变
  1160. var s = this.info.modelBound.scaleRatio
  1161. this.mesh.scale.set(s,s,s)
  1162. this.mesh.position.fromArray(this.info.modelBound.position)
  1163. this.mesh.modelBound = this.info.modelBound
  1164. }
  1165. this.material_.side = THREE.DoubleSide //模型单面的话不好校准位置
  1166. this.changeBoxHelperDisplay(false)
  1167. //this.mesh.boxHelper.visible = true
  1168. }
  1169. addPlane(){//换成plane
  1170. if(this.plane)return
  1171. this.plane = new THREE.Mesh(_planeGeometry, this.material_)
  1172. this.remove(this.objObject)
  1173. this.objObject = null
  1174. delete this.info.objSrc
  1175. delete this.info.objName
  1176. delete this.info.modelBound
  1177. this.setMesh(this.plane)
  1178. this.material_.side = THREE.FrontSide //双面的话飞出来会看到热点悬空的
  1179. }
  1180. setMesh(mesh){
  1181. this.mesh = mesh
  1182. this.add(this.mesh);
  1183. this.changeMaterial(this.material_) //re applyTo every mesh
  1184. this.mesh.traverse((mesh)=>{
  1185. mesh.type = "hotSprite" //raycaster use
  1186. mesh.renderOrder = 3
  1187. })
  1188. if(!this.mesh.boxHelper){
  1189. var boxHelper = this.mesh.children.find(e=>e instanceof THREE.Box3Helper)
  1190. if(boxHelper){
  1191. this.mesh.boxHelper = boxHelper
  1192. }else{
  1193. var bound = this.getBoundOri()
  1194. bound.expandByVector(new THREE.Vector3(0.0001,0.0001,0.0001))
  1195. this.mesh.boxHelper = new THREE.Box3Helper( bound, new THREE.Color( "#00ffff"));
  1196. this.mesh.add(this.mesh.boxHelper)
  1197. this.mesh.boxHelper.material.depthTest = false;
  1198. this.mesh.boxHelper.material.transparent = true
  1199. }
  1200. convertTool.updateVisible(this.mesh.boxHelper,'defaultHide', false)//默认隐藏
  1201. }
  1202. }
  1203. changeBoxHelperDisplay(show,reason='hover'){
  1204. if(show){//暂时先把模型强制显示,以展示 boxHelper
  1205. convertTool.updateVisible(this, 'showBoxHelper', true, 2, 'add')
  1206. convertTool.updateVisible(this.mesh.boxHelper, 'highlight_'+reason, true, 2, 'add')
  1207. }else{
  1208. convertTool.updateVisible(this, 'showBoxHelper', false, 2, 'cancel')
  1209. convertTool.updateVisible(this.mesh.boxHelper, 'highlight_'+reason, false, 2, 'cancel')
  1210. }
  1211. }
  1212. /* addToLoadQueue() {
  1213. if (this.texType == 'photo') {
  1214. Hot.loadQueue.includes(this) || Hot.loadQueue.push(this)
  1215. }
  1216. } */
  1217. requestDownload(type, callback) {
  1218. var plane = this.plane;
  1219. if(type == 'photo'){
  1220. if(this.photoHasRequestLoad || this.texType != 'photo'){
  1221. return; //为什么之前1191需要在这加callback() 才能呢。现在又没事了
  1222. }
  1223. //console.log(' beginDownload : ' + this.sid)
  1224. Texture.load(this.info.texSrc, (tex)=>{
  1225. callback && callback()
  1226. if(!tex.image ){
  1227. return //只是单纯用了相同src的tex,但image仍未加载完
  1228. }
  1229. if(!this._loadDones){
  1230. return
  1231. }
  1232. dealMap(tex)
  1233. setTimeout(Hot.loadNext, 50)
  1234. hotGroup.children.forEach(e=>{
  1235. if(e.info.texSrc == this.info.texSrc && e.info.texType == type ){
  1236. e.material_.color.set("#FFFFFF")
  1237. e.material_.opacity = 1;
  1238. //console.log('overlay loaded: ' + e.sid + " - " + this.info.texSrc.split('/').pop());
  1239. e.texMedia = tex.image
  1240. {//animation不同致使的不能使用同一个texture
  1241. if(window.isEdit){
  1242. if(animateTexSrcs[e.info.texSrc]){
  1243. e.material_.map = tex.clone(); //编辑动画直接不用一个texture, 故而animation也不同
  1244. e.material_.map.repeat.set(1,1), e.material_.map.offset.set(0,0)
  1245. e.material_.map.needsUpdate = true
  1246. }else{
  1247. e.material_.map = tex
  1248. animateTexSrcs[e.info.texSrc] = 1
  1249. }
  1250. }else{
  1251. if(animateTexSrcs[e.info.texSrc]){//已有该texSrc
  1252. let finded = false
  1253. for(let i of animateTexSrcs[e.info.texSrc]){
  1254. if(ifSame(i[0], e.info.animateInfo)){
  1255. e.material_.map = i[1]; finded = true; break;
  1256. }
  1257. }
  1258. if(!finded){
  1259. let tex_ = tex.clone();
  1260. tex_.repeat.set(1,1), tex_.offset.set(0,0)
  1261. tex_.needsUpdate = true //clone后不写这句会黑块
  1262. animateTexSrcs[e.info.texSrc].set(e.info.animateInfo, tex_)
  1263. e.material_.map = tex_
  1264. }
  1265. }else{
  1266. let object = new Map();
  1267. object.set(e.info.animateInfo, tex)
  1268. animateTexSrcs[e.info.texSrc] = object//注册第一个texSrc
  1269. e.material_.map = tex
  1270. }
  1271. }
  1272. }
  1273. if(e.info.animateInfo && !e.animation){
  1274. e.animation = GifTexDeal.addAnimation(e.material_.map, e, e.info.animateInfo, e.sid )
  1275. e.visible && e.inSight() && GifTexDeal.start(e.animation)
  1276. }
  1277. if(++photoLoaded == originPhotoCount){//data2.js中的所有photo加载完毕
  1278. Hot.allPhotoLoaded = true;
  1279. Hot.whenAllFileLoaded && Hot.allModelLoaded && Hot.whenAllFileLoaded()
  1280. }
  1281. {
  1282. e._loadDones.forEach(a=>a())
  1283. e._loadDones = null
  1284. //e.photoHasRequestLoad = true //这句不能加,否则会无法执行callback
  1285. }
  1286. e.material_.needsUpdate = true
  1287. }
  1288. })
  1289. })
  1290. this.photoHasRequestLoad = true
  1291. }else if(type == 'model'){
  1292. if(this.modelHasRequestLoad || !this.info.objSrc)return;
  1293. objLoader.load(this.info.objSrc, (object)=>{
  1294. this.remove(this.mesh)
  1295. this.addModel(object) //复制的也包含boxHelper(也是复制的)
  1296. callback && callback()
  1297. if(++modelLoaded == originModelCount){//data2.js中的所有photo加载完毕
  1298. Hot.allModelLoaded = true;
  1299. Hot.whenAllFileLoaded && Hot.allPhotoLoaded && Hot.whenAllFileLoaded()
  1300. }
  1301. })
  1302. this.modelHasRequestLoad = true
  1303. }
  1304. }
  1305. dispose(){
  1306. this.parent.remove(this)
  1307. this.titleElem && this.titleElem.dispose()
  1308. delete player.model.hots[this.sid]
  1309. }
  1310. }
  1311. Hot.updateVisibles = function(panos, type) { //只显示没被遮挡的,否则会卡
  1312. if (panos === true) {
  1313. model.hotGroup.children.forEach(e=>e.updateVisible(null,true,type))
  1314. } else {
  1315. model.hotGroup.children.forEach(e=>e.updateVisible(panos, null, type))
  1316. }
  1317. }
  1318. Hot.beginShineHot = function(){
  1319. if(!window.isEdit && shineMats.length == 0)return
  1320. transitions.trigger({
  1321. func: function(e) {
  1322. var opa = e <= .5 ? 2 * e : -2 * e + 2
  1323. shineMats.forEach(mat=>{
  1324. mat.uniforms.opac.value = opa;
  1325. })
  1326. },
  1327. cycling: !0,
  1328. duration: 3e3,
  1329. name: "hotShine"
  1330. })
  1331. }
  1332. Hot.getDefaulScale = function(hotIconScale){
  1333. return (hotIconScale || DATA.hotIconScale) * g_HotMeshSize.g_HotMeshWidth
  1334. }
  1335. var loadings = [];
  1336. Hot.loadQueue = []; //等待下载的overlay,目前只针对photo
  1337. Hot.maxLoadingCount = 3; //同时正在load图片的数量
  1338. Hot.loadNext = ()=>{//继续requestDownload loadQueue中前排的item
  1339. let count = Hot.maxLoadingCount - loadings.length
  1340. Hot.loadQueue.slice(0, count).forEach(e=>{
  1341. loadings.push(e)
  1342. //console.log('requestDownload', e.hot.info.texSrc)
  1343. e.hot.requestDownload(e.type, ()=>{
  1344. var i = loadings.indexOf(e)
  1345. //console.log('requestDownloaded index', i)
  1346. i > -1 && loadings.splice(i,1)
  1347. })
  1348. })
  1349. Hot.loadQueue.splice(0, count)
  1350. }
  1351. Hot.getNeedLoad = function() {//计算获取loadQueue,每次都重新计算,覆盖旧的
  1352. if (!player || !player.domElement || !player.mode)
  1353. return;
  1354. var hots1, hots2
  1355. if (player.mode != 'panorama') {
  1356. if (Hot.loadQueue.length == 0) {
  1357. hots1 = model.hotGroup.children.filter(e=>e.texType == 'photo' && !e.photoHasRequestLoad )
  1358. hots2 = model.hotGroup.children.filter(e=>e.info.objSrc && !e.modelHasRequestLoad )
  1359. Hot.loadQueue = hots1.map(e=>{return {hot:e, type:"photo"}}).concat(
  1360. hots2.map(e=>{return {hot:e, type:"model"}})
  1361. )
  1362. }
  1363. return
  1364. }
  1365. //Hot.loadWhenOutside = true
  1366. hots1 = model.hotGroup.children.filter(e=>e.texType == 'photo' && !e.photoHasRequestLoad && (!e.info.visiblePanos || e.info.visiblePanos.includes(player.currentPano.id)))
  1367. hots2 = model.hotGroup.children.filter(e=>e.info.objSrc && !e.modelHasRequestLoad && (!e.info.visiblePanos || e.info.visiblePanos.includes(player.currentPano.id)))
  1368. if(hots1.length+hots2.length == 0){
  1369. hots1 = model.hotGroup.children.filter(e=>e.texType == 'photo' && !e.photoHasRequestLoad )
  1370. hots2 = model.hotGroup.children.filter(e=>e.info.objSrc && !e.modelHasRequestLoad )
  1371. }
  1372. var cameraDir = player.getDirection()
  1373. Hot.loadQueue = hots1.map(e=>{return {hot:e, type:"photo"}}).concat(
  1374. hots2.map(e=>{return {hot:e, type:"model"}})
  1375. )
  1376. var request = [(item)=>{
  1377. return true
  1378. }];
  1379. var rank = [(item)=>{
  1380. var dis = item.hot.getWorldCenter().distanceTo(player.position);
  1381. return -dis
  1382. }
  1383. , (item)=>{
  1384. var tagDir = item.hot.getWorldCenter().sub(player.position) //mesh.getWorldPosition()
  1385. var angle = tagDir.angleTo(cameraDir)
  1386. return -angle * 20
  1387. }]
  1388. var result = common.sortByScore(Hot.loadQueue, request, rank);
  1389. //Hot.loadQueue = result ? result.slice(0, 5).map(e=>e.item) : model.hotGroup.children.filter(e=>e.texType == 'photo' && !e.hasRequestLoad).slice(0, 2);
  1390. Hot.loadQueue = result ? result.slice(0, 5).map(e=>e.item) : []
  1391. }
  1392. Hot.load = ()=>{//开始下载图片
  1393. Hot.getNeedLoad()
  1394. Hot.loadNext()
  1395. var hots1 = model.hotGroup.children.filter(e=>e.texType == 'photo' && !e.photoHasRequestLoad )
  1396. var hots2 = model.hotGroup.children.filter(e=>e.info.objSrc && !e.modelHasRequestLoad )
  1397. if (hots1.length+hots2.length > 0) {
  1398. setTimeout(Hot.load, 200)
  1399. } else {
  1400. Hot.allRequestLoad = true
  1401. console.log('allRequestLoad')
  1402. }
  1403. }
  1404. Hot.startLoad = ()=>{
  1405. originPhotoCount = hotGroup.children.filter(e=>e.texType == 'photo').length
  1406. originModelCount = hotGroup.children.filter(e=>!!e.info.objSrc).length
  1407. if(originPhotoCount == 0 )Hot.allPhotoLoaded = true
  1408. if(originModelCount == 0) Hot.allModelLoaded = true;
  1409. if(Hot.allModelLoaded && Hot.allPhotoLoaded) Hot.whenAllFileLoaded && Hot.whenAllFileLoaded()//所有加载完毕
  1410. else{
  1411. Hot.load()
  1412. }
  1413. setTimeout(Hot.beginShineHot, 1000)
  1414. if(!window.isEdit){
  1415. if(playSyncGroup.length){
  1416. //每过一段时间校准同步视频的时间,使与第一个视频同步
  1417. setInterval(()=>{
  1418. playSyncGroup.forEach(group=>{
  1419. group = group.map(e=>player.model.hots[e].texMedia).filter(e=>!e.paused)
  1420. if(group.length < 2)return
  1421. //console.log('同步',group.map(e=>e.currentTime))
  1422. for(let i=group.length-1;i>0;i--){
  1423. group[i].currentTime = group[0].currentTime
  1424. }
  1425. })
  1426. },4000)
  1427. }
  1428. }
  1429. player.emit('gotHotAndStartload')
  1430. if(player.model.floors.list.length>1){
  1431. player.model.on("floor.changed",(currentFloor, mode, oldFloor)=>{
  1432. //this.update(currentFloor) //注: currentFloor 这时候还没成为 model.currentFloor
  1433. let showAll = mode == 'panorama' || player.model.allFloorsVisible
  1434. hotGroup.children.forEach(e=>{
  1435. convertTool.updateVisible(e,'floor',showAll || e.floor == currentFloor)
  1436. })
  1437. //console.log(currentFloor, mode, oldFloor)
  1438. })
  1439. }
  1440. }
  1441. window.Hot = Hot
  1442. /* var ball = new THREE.Mesh(new THREE.SphereBufferGeometry(0.01),new THREE.MeshBasicMaterial({color:"#f00",depthTest:false,transparent:true}))
  1443. var balls = []
  1444. var addPoint = function(point){
  1445. console.log(point)
  1446. var ball1 = ball.clone()
  1447. model.add(ball1);
  1448. ball1.position.copy(point)
  1449. balls.push(ball1)
  1450. }
  1451. var addPoints = function(points){
  1452. balls.forEach(e=>model.remove(e))
  1453. balls = []
  1454. points.forEach(e=>addPoint(e))
  1455. }
  1456. */
  1457. //判断是否是移动端,如果是给关闭按钮添加touchstart事件
  1458. Hot.closePopup = ()=>{// 关闭热点页面
  1459. if(!g_currentHot) return;
  1460. g_currentHot = null;
  1461. var hotPop = document.getElementById('popup');
  1462. hotPop.style.display = "none";
  1463. document.querySelector(".popup-content").removeChild(document.getElementById("id1"));
  1464. $("#popup iframe:last").remove();
  1465. SoundManager.pause('hot', true)//自动播放被中断的音频 (bgm
  1466. return false
  1467. }
  1468. if(browser.isMobile()){
  1469. $('#closepop').on("touchstart",Hot.closePopup);
  1470. }else{
  1471. $('#closepop').on("click",Hot.closePopup);
  1472. }
  1473. Hot.createHotList = function() {
  1474. if(!window.DATA.showHotListSta)return
  1475. var docFragment = document.createDocumentFragment();
  1476. var hots = hotGroup.children.filter(hot=>hot.info.actionType.openHot);
  1477. hots = hots.sort((a,b)=>{return a.order - b.order});
  1478. hots.forEach((hot)=>{
  1479. var li = document.createElement('li');
  1480. var span = document.createElement('span');
  1481. span.innerHTML = hot.info.title || '热点';
  1482. // console.log(span.innerHTML);
  1483. li.hot = hot; // 列表每一项对应一个热点
  1484. li.appendChild(span);
  1485. docFragment.appendChild(li);
  1486. })
  1487. setTimeout(()=>{
  1488. var ul = document.querySelector('#hotListContent ul');
  1489. ul && ul.appendChild(docFragment);
  1490. },3000) //部分安卓手机的钉钉和支付宝浏览器如果立即添加到列表会卡住
  1491. }
  1492. Hot.mobileAutoPlay = function(player){//移动端。不这么写video不会播放 . (2022.11.29: 可为何加了Hot.updateHots之后又会自动播了?https有关?
  1493. if(browser.isMobile()){
  1494. hotGroup.children.forEach((hot)=>{
  1495. /* if(hot.texType == 'video' && hot.shouldPlay){
  1496. console.log(1)
  1497. } */
  1498. if(!hot.clickToPlayInited && hot.texType == 'video' && !isVideoPlayed(hot.texMedia) && hot.shouldPlay){
  1499. //hot.update(player)
  1500. console.log({str:'try mobileAutoPlay '+ hot.sid, level:1})
  1501. hot.videoControl(true)
  1502. if(isVideoPlayed(hot.texMedia)){
  1503. console.log({str:'clickToPlayInited '+ hot.sid, level:1})
  1504. hot.clickToPlayInited = true
  1505. hot.changeOpaWhenPlay(video)
  1506. }
  1507. }
  1508. })
  1509. }
  1510. }
  1511. Hot.updateHots = function(){
  1512. for(var i in player.model.hots){
  1513. player.model.hots[i].update(player)
  1514. }
  1515. if( !player.ready)return
  1516. this.needUpdate = true
  1517. common.intervalTool.isWaiting('updateHots', ()=>{ //延时update,防止卡顿
  1518. if(!this.needUpdate)return
  1519. this.needUpdate = false
  1520. let videoCanPlay = [], aniCanPlay = [];
  1521. for(var i in player.model.hots){
  1522. let hot = player.model.hots[i]
  1523. {//实时监测播放
  1524. if(hot.texType == "video" && !player.flying){ //飞行时不判断
  1525. if(hot.visible && !hot.pausedByUser && hot.inSight()){ //注意edit时insight一直为true
  1526. //this.videoControl(true)
  1527. videoCanPlay.push(hot)
  1528. }else{
  1529. hot.videoControl(false)
  1530. }
  1531. }else if(hot.info.animateInfo){
  1532. if(hot.visible && hot.inSight()){
  1533. aniCanPlay.push(hot)
  1534. }else{
  1535. GifTexDeal.stop(hot.animation)
  1536. }
  1537. }
  1538. }
  1539. }
  1540. let filter = (type,max,list,playFun)=>{
  1541. if(list.length < max){
  1542. list.forEach(hot=>type == 'video' ? hot.videoControl(true) : GifTexDeal.start(hot.animation))
  1543. }else{
  1544. let playerDir = player.getDirection()
  1545. let request = []
  1546. let planeDir = new Map()
  1547. if(player.mode != 'panorama'){
  1548. request.push((hot)=>{
  1549. if(hot.plane){//考虑plane的方向,它甚至可能背对镜头
  1550. let dir = new THREE.Vector3(0,0,-1).applyQuaternion(hot.quaternion)
  1551. let angle = dir.dot(playerDir)
  1552. planeDir.set(hot, angle)
  1553. return angle > 0
  1554. }else return true
  1555. })
  1556. }
  1557. let r = common.sortByScore(list,request,[(hot)=>{ //方向因素
  1558. var cornerPointInfo = hot.getCornerPoint(); //仅支持漫游模式
  1559. let dir = new THREE.Vector3().subVectors(hot.position, player.position).normalize()
  1560. score = dir.dot(playerDir)
  1561. player.mode == 'panorama' && (score *= Math.pow(cornerPointInfo.diffLat,0.5))// pow降低高度的权重,因为宽度更重要些。之所以两个函数都乘以diffLat,也是为了防止在两个item的分数相同diffLon不同时,diffLat增长相同倍数却能造成分数差异的情况
  1562. return score
  1563. },
  1564. (hot)=>{//面积因素
  1565. if(player.mode == 'panorama'){
  1566. var cornerPointInfo = hot.getCornerPoint(); //仅支持漫游模式
  1567. let area = cornerPointInfo.diffLon * Math.pow(cornerPointInfo.diffLat,0.5) / 1000 //占据面积
  1568. return area
  1569. }else{
  1570. let dis = player.position.distanceTo(hot.position);
  1571. let size = hot.getBoundOri().applyMatrix4(hot.matrixWorld).size(new THREE.Vector3).length() / 2
  1572. let score = Math.atan(size / Math.pow(dis,1.2)) * 50 // 在镜头中所占fov angle 的一半 。 pow是因为实际感受还是尽量显示近处的
  1573. if(hot.plane){//考虑plane的方向,越倾斜分越低
  1574. score *= planeDir.get(hot)
  1575. }
  1576. return score
  1577. }
  1578. } ])
  1579. //getCornerPoint仅支持漫游模式 Lat高度(纬度).
  1580. //console.log(r)
  1581. r && r.forEach((e,j)=>{
  1582. let hot = e.item
  1583. if(j<max){
  1584. type == 'video' ? hot.videoControl(true) : GifTexDeal.start(hot.animation)
  1585. }else{
  1586. type == 'video' ? hot.videoControl(false) : GifTexDeal.stop(hot.animation)
  1587. }
  1588. })
  1589. }
  1590. }
  1591. filter('video',playVideoMax, videoCanPlay);
  1592. filter('animateInfo',playAniMax, aniCanPlay);
  1593. //console.log('updateHots')
  1594. return true
  1595. }, 800)
  1596. }
  1597. }
  1598. function isVideoPlayed(video){
  1599. return !video.paused && !isNaN(video.duration) //注意,有的手机首次play时会立即paused为false,但其实没加载好, duration为NAN
  1600. }
  1601. /*
  1602. 保存 JSON.stringify(editTool.hotpoint.getSavingInfo())
  1603. 可能需要再写一份保存到overlay 给旧场景项目使用
  1604. 最好后台有针对手机版的做一个压缩。压缩成几个档位。
  1605. 安卓手机firefox出现过视频mesh不可见或者闪烁的情况。
  1606. 视频最容易导致崩溃, 模型还好
  1607. 数据速率为4064kbps,1920*1080px 时测试部门电脑崩溃
  1608. 数据速率为1824kbps,720 *576px 时正常
  1609. 所以尽量降到2000以下 同时播放个数最好不超过2个 可能需要将src归零 并延迟加载、不自动播放
  1610. videoWidth和帧宽度竟然不一定一样,帧宽度3840,帧高度2160,但是竖屏视频,videoWidth为3840,videoHeight为6833
  1611. video宽高达到 videoWidth 3840, videoHeight 6833时 在安卓无法播放。可能是高于4096的原因。
  1612. 有遇到某张图在ios里显示不出,但在ps重新输出后就可以。这张图的dpi高达500,不知道是否与此有关。
  1613. 遇到过ios14.8.1、14.7.1 无法播放webm(来自1050)。 onloadedmetadata失败
  1614. 显示故障的机型:
  1615. 视频黑屏(4dkk视频漫游点也无法播放):小米10(自带浏览器)、红米K40(微信浏览器)、
  1616. 贴图safari黑色的,很可能是使用CMYK模式的原因,dpi>96了
  1617. */