objViewer.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. let texLoader = new THREE.TextureLoader;
  2. var emissiveTexture = null
  3. let camera, scene, renderer, stats, gui;
  4. const mouse = new THREE.Vector2();
  5. const raycaster = new THREE.Raycaster(); raycaster.linePrecision = 0;//不检测boxHelper
  6. const Transitions = {
  7. doubleClick: 0,
  8. helperOpa: 1,
  9. }
  10. let labelIndex = 0
  11. var Viewer = function (index, dom) {
  12. THREE.EventDispatcher.call(this)
  13. this.index = index;
  14. this.dom = dom
  15. this.camera = new THREE.PerspectiveCamera();
  16. this.camera.position.set(0, 0, 0.78);
  17. this.control = new THREE.OrbitControls(this.camera, this.dom)
  18. this.control.enableDamping = true;
  19. this.control.dampingFactor = 0.4;
  20. this.control.minDistance = 0.3;
  21. this.control.maxDistance = 2;
  22. this.control.enablePan = false;
  23. this.control.enableZoom = true;
  24. this.setRenderer()
  25. this.scene = new THREE.Scene;
  26. this.pointerDownPos
  27. this.textures = [];
  28. this.labels = []
  29. this.active = false;
  30. this.antialias = true;
  31. this.clickTime = new Date().getTime();
  32. this.updateClock = new THREE.Clock;
  33. this.init()
  34. }
  35. Viewer.prototype = Object.create(THREE.EventDispatcher.prototype)
  36. Viewer.constructor = Viewer
  37. Viewer.prototype.bindEvents = function () {
  38. this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown.bind(this), false);
  39. this.renderer.domElement.addEventListener('pointerup', this.onPointerUp.bind(this), false);
  40. this.renderer.domElement.addEventListener('pointermove', this.onPointerMove.bind(this), false);
  41. }
  42. Viewer.prototype.setRenderer = function () {
  43. try {
  44. this.renderer = new THREE.WebGLRenderer(
  45. {
  46. canvas: $(this.dom).find("canvas")[0],
  47. antialias: true,
  48. alpha: true
  49. }
  50. ),//许钟文 添加个抗锯齿,否则添加的线条锯齿严重,
  51. this.renderer.setClearAlpha(0);
  52. //this.renderer.autoClear = !0,
  53. this.renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1)
  54. // this.renderer.autoClear = false
  55. //this.emit(Events.ContextCreated)
  56. } catch (e) {
  57. console.error("Unable to create a WebGL rendering context")
  58. }
  59. }
  60. Viewer.prototype.update = function (deltaTime) {//绘制的时候同时更新
  61. //if(!this.active)return;
  62. this.setSize()
  63. transitions.update(deltaTime)
  64. this.control.update(deltaTime)//写在transitions后面否则镜头抖动
  65. var needsUpdate = 1;
  66. if (needsUpdate) {
  67. //this.renderer.autoClear = true
  68. this.renderer.render(this.scene, this.camera)
  69. }
  70. }
  71. Viewer.prototype.hasChanged = function () {//判断画面是否改变了,改变后需要更新一些东西
  72. var copy = function () {
  73. this.previousState = {
  74. projectionMatrix: this.camera.projectionMatrix.clone(),//worldMatrix在control时归零了所以不用了吧,用position和qua也一样
  75. position: this.camera.position.clone(),
  76. quaternion: this.camera.quaternion.clone(),
  77. //mouse: this.mouse.clone(),
  78. fov: this.camera.fov
  79. };
  80. }.bind(this)
  81. if (!this.previousState) {
  82. copy()
  83. return { cameraChanged: !0/* , changeSlightly: !1 */ };
  84. }
  85. var cameraChanged =
  86. !this.camera.projectionMatrix.equals(this.previousState.projectionMatrix) ||
  87. !this.camera.position.equals(this.previousState.position) ||
  88. !this.camera.quaternion.equals(this.previousState.quaternion)
  89. //var changed = cameraChanged //|| !this.mouse.equals(this.previousState.mouse)
  90. /* let changeSlightly
  91. if (cameraChanged) {
  92. changeSlightly = math.closeTo(this.camera.position, this.previousState.position, 1e-2) &&
  93. math.closeTo(this.camera.quaternion, this.previousState.quaternion, 1e-3)
  94. } */
  95. copy()
  96. return { cameraChanged/* , changeSlightly */};
  97. }
  98. Viewer.prototype.setSize = function () {
  99. var w, h, pixelRatio;
  100. return function () {
  101. if (w != this.dom.clientWidth || h != this.dom.clientHeight || pixelRatio != window.devicePixelRatio) {
  102. w = this.dom.clientWidth;
  103. h = this.dom.clientHeight;
  104. pixelRatio = window.devicePixelRatio;
  105. this.camera.aspect = w / h;
  106. this.camera.updateProjectionMatrix();
  107. this.renderer.setSize(w, h, false, pixelRatio);
  108. }
  109. }
  110. }()
  111. Viewer.prototype.init = function () {
  112. this.meshGroup = new THREE.Object3D();
  113. this.scene.add(this.meshGroup);
  114. this.meshGroup.name = "viewerMeshGroup";
  115. var buildScene = () => {
  116. this.animate()
  117. }
  118. this.loadOBJ(() => {
  119. this.bindEvents()
  120. //this.loadLabels()
  121. })
  122. buildScene()
  123. }
  124. let delayNeedUpdated
  125. Viewer.prototype.animate = function () {
  126. var deltaTime = Math.min(1, this.updateClock.getDelta());
  127. this.update(deltaTime)
  128. //bus.emit('player/position/change', {x:this.position.x, y:this.position.z, lon: this.cameraControls.controls.panorama.lon})
  129. let changed = this.hasChanged()
  130. if (changed.cameraChanged) {
  131. this.dispatchEvent({ type: 'view.changed'/* , changeSlightly: changed.changeSlightly */ })
  132. delayNeedUpdated = true
  133. if (!transitions.funcs.some(function (e) { return e.name.includes('cameraFly') })) {
  134. convertTool.intervalTool.isWaiting('delayUpdate', () => { //延时update,防止卡顿
  135. if (delayNeedUpdated) {
  136. delayNeedUpdated = false
  137. this.dispatchEvent({ type: 'delayUpdate' })
  138. return true
  139. }
  140. }, 300)
  141. }
  142. let label_ = this.labels.filter(e => e.elem[0].style.display == 'block')
  143. label_.sort((a, b) => b.pos2d.z - a.pos2d.z)
  144. label_.forEach((e, index) => e.elem.css('z-index', index + 1000));
  145. }
  146. window.requestAnimationFrame(this.animate.bind(this));
  147. },
  148. Viewer.prototype.loadOBJ = function (done) {
  149. var startTime = new Date().getTime();
  150. window.objs = [];
  151. var group = new THREE.Object3D;
  152. this.meshGroup.add(group);
  153. function onProgress(xhr) {
  154. if (xhr.lengthComputable) {
  155. var percentComplete = xhr.loaded / xhr.total * 100;
  156. console.log('model ' + Math.round(percentComplete, 2) + '% downloaded');
  157. }
  158. }
  159. function onError() { }
  160. var MTLLoader = new THREE.MTLLoader();
  161. var OBJLoader = new THREE.OBJLoader()
  162. var loadModel = () => {
  163. var info = {//凳子
  164. path: 'model/',
  165. mtl: 'wl48-he.mtl',
  166. obj: 'wl48-he.obj',
  167. position: [0, 0],
  168. rotation: 0,
  169. height: 1
  170. };
  171. if (!info) {
  172. console.log("加载持续时间:" + (new Date().getTime() - startTime))
  173. return;
  174. }
  175. MTLLoader.setPath(info.path).load(info.mtl, (materials) => {
  176. materials.preload();
  177. OBJLoader.setMaterials(materials)
  178. .setPath(info.path)
  179. .load(info.obj, (object) => {
  180. group.add(object);
  181. object.traverse(function (child) {
  182. if (child.isMesh) {
  183. if (child.name == "WL48_ping") {
  184. let textrueLoader = new THREE.TextureLoader();
  185. emissiveTexture = textrueLoader.load("model/default.jpg");
  186. let step = 1
  187. setInterval(() => {
  188. if (window.showWenli) {
  189. if (child.material.emissiveIntensity > 0.5) {
  190. step = -1
  191. }
  192. if (child.material.emissiveIntensity < 0) {
  193. step = 1
  194. }
  195. child.material.emissiveIntensity += 0.01 * step;
  196. }
  197. else {
  198. child.material.emissiveMap = emissiveTexture;
  199. child.material.emissiveIntensity = 0;
  200. }
  201. }, 50);
  202. child.material.emissive = new THREE.Color(0xffffff);
  203. child.material.dispose();
  204. }
  205. /* if(child.geometry){
  206. child.geometry.computeBoundingBox();
  207. bound.union(child.geometry.boundingBox)
  208. } */
  209. }
  210. });
  211. this.model = object
  212. let s = 0.010
  213. object.scale.set(s, s, s)
  214. done && done()
  215. console.log("加载持续时间:" + (new Date().getTime() - startTime))
  216. setTimeout(() => {
  217. this.dispatchEvent({ type: 'hadLoaded' })
  218. });
  219. }, onProgress, onError);
  220. });
  221. }
  222. loadModel()
  223. var light1 = new THREE.AmbientLight(16777215);
  224. light1.intensity = 2.8;
  225. this.scene.add(light1)
  226. var light2 = new THREE.SpotLight(0xffffff, 1);
  227. light2.position.set(0, 0, 3)
  228. light2.intensity = 0.2;
  229. var light3 = new THREE.SpotLight(0xffffff, 1);
  230. light3.position.set(0, 0, -3)
  231. light3.intensity = 0.4;
  232. // let spotLightHelper = new THREE.SpotLightHelper(light2);
  233. // let spotLightHelper3 = new THREE.SpotLightHelper(light3);
  234. this.scene.add(light2)
  235. this.scene.add(light3)
  236. // this.scene.add( spotLightHelper );
  237. // this.scene.add( spotLightHelper3 );
  238. }
  239. Viewer.prototype.onPointerMove = function (event) {
  240. if (event.isPrimary === false) return;
  241. mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  242. mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  243. if (!this.pointerDownPos){
  244. //this.checkIntersection();
  245. }else{
  246. transitions.cancelByName('cameraFly2')
  247. }
  248. //console.log('onPointerMove', this.pointerDownPos)
  249. }
  250. Viewer.prototype.onPointerDown = function (event) {
  251. if (event.isPrimary === false) return;
  252. mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  253. mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  254. this.pointerDownPos = mouse.clone()
  255. //console.log('onPointerDown')
  256. }
  257. Viewer.prototype.onPointerUp = function (event) {
  258. if (event.isPrimary === false) return;
  259. this.dispatchEvent({ type: 'onPointerUp' })
  260. mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  261. mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  262. if (this.pointerDownPos && mouse.distanceTo(this.pointerDownPos) < 0.006) {//click
  263. this.checkIntersection()
  264. // if (this.intersects.length) {
  265. // console.log(this.intersects[0].point);
  266. // this.addLabel({ position: this.intersects[0].point })
  267. // }
  268. // var time = new Date().getTime();
  269. // if (time - this.clickTime < 300) {
  270. // if (this.intersects.length) {
  271. // console.log('doubleClick');
  272. // transitions.cancelById(0)
  273. // transitions.start(lerp.vector(this.control.target, this.intersects[0].point), 600, null, 0/* Delay */, easing.easeInOutQuad, null, Transitions.doubleClick);
  274. // }
  275. // }
  276. // this.clickTime = time;
  277. }
  278. this.pointerDownPos = null
  279. //console.log('onPointerUp')
  280. }
  281. Viewer.prototype.checkIntersection = function () {
  282. raycaster.setFromCamera(mouse, this.camera);
  283. const intersects = raycaster.intersectObject(this.model/* this.meshGroup */, true);
  284. this.intersects = intersects;
  285. }
  286. /* Viewer.prototype.adjustModelPos = function(){ //固定地板高度的情况下,调整模型的position.y
  287. this.meshGroup.updateMatrixWorld();
  288. this.model.updateMatrixWorld();
  289. var bound = this.model.bound.clone().applyMatrix4(this.model.matrixWorld)
  290. var center = bound.getCenter()
  291. //为了让最低点在地面上:
  292. this.meshGroup.position.y += floorY - bound.min.y
  293. //居中:
  294. this.meshGroup.position.x += 0 - center.x
  295. this.meshGroup.position.z += 0 - center.z
  296. } */
  297. Viewer.prototype.removeAllLabels = function (data) {
  298. this.labels.forEach(label => {
  299. label.dispose()
  300. label = null
  301. })
  302. this.labels = []
  303. }
  304. Viewer.prototype.loadLabelsFromData = function (data) {
  305. data.forEach(info => {
  306. info.position = new THREE.Vector3().fromArray(info.posInModel).applyMatrix4(this.model.matrixWorld)
  307. this.addLabel(info)
  308. })
  309. }
  310. Viewer.prototype.addLabel = function (o) {
  311. labelIndex++
  312. o.title = o.title || ('default' + labelIndex)
  313. o.shelterByModel = true
  314. let label = new Label2D(o)
  315. this.labels.push(label)
  316. }
  317. Viewer.prototype.removeLabel = function (label) {
  318. label.dispose()
  319. let index = this.labels.indexOf(label)
  320. index > -1 && this.labels.splice(index)
  321. label.li && label.li.remove()
  322. }
  323. Viewer.prototype.setAddLabelState = function (state) {
  324. this.addingLabel = !!state
  325. $('#addLabel').text(state ? '停止加标签' : '添加标签')
  326. }
  327. Viewer.prototype.exportLabelData = function () {
  328. let data = this.labels.map(label => {
  329. let inv = new THREE.Matrix4().getInverse(this.model.matrixWorld)
  330. let posInModel = label.position.clone().applyMatrix4(inv)
  331. let info = {
  332. title: label.title,
  333. posInModel: convertTool.toPrecision(posInModel.toArray(), 4),
  334. }
  335. return info
  336. })
  337. $('textarea').css('display', 'block').text(JSON.stringify(data))
  338. console.log(data)
  339. return data
  340. }
  341. Viewer.prototype.getCLabel = function () { //获取最接近中心的label
  342. let disSquairs = this.labels.filter(e => e.elem[0].style.display == 'block').map((label) => {
  343. return {
  344. label,
  345. disSquair: label.pos2d.x * label.pos2d.x + label.pos2d.y * label.pos2d.y
  346. }
  347. })
  348. disSquairs.sort((e1, e2) => { return e1.disSquair - e2.disSquair })
  349. return disSquairs[0] && disSquairs[0].label
  350. }
  351. //============
  352. var startTime = new Date().getTime();
  353. function dataURLtoBlob(dataurl) {//将base64转换blob
  354. var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
  355. bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  356. while (n--) {
  357. u8arr[n] = bstr.charCodeAt(n);
  358. }
  359. return new Blob([u8arr], { type: mime });
  360. }
  361. /*
  362. MeshStandardMaterial(pbr)代替Phong
  363. 优点 : 能量守恒、更容易调节出真实感。 metalnessMap只用到一个通道,颜色信息整合到albedo贴图里,省数据。
  364. 缺点:可能,mtl里的值只能用到一部分, specular用不到。 (会降低对specular的控制)
  365. 另外不知道大部分模型用的是哪种模式,是否使用metalnessMap。
  366. aoMap r
  367. roughnessMap alphaMap g
  368. metalnessMap b
  369. normalMap ?
  370. bumpMap : 黑白?
  371. 主要是光滑度or粗糙度(可贴图) 还有 金属性(可贴图) 还有颜色(可贴图),透明度(in颜色贴图),法线贴图or凹凸贴图
  372. 贴图的属性 rotation offset repeat (当wrapS = THREE.RepeatWrapping,wrapT = THREE.RepeatWrapping)
  373. */