let texLoader = new THREE.TextureLoader; let camera, scene, renderer, stats, gui; const mouse = new THREE.Vector2(); const raycaster = new THREE.Raycaster(); raycaster.linePrecision = 0;//不检测boxHelper const Transitions = { doubleClick: 0, helperOpa: 1, } let labelIndex = 0 var Viewer = function (index, dom) { THREE.EventDispatcher.call(this) this.index = index; this.dom = dom this.camera = new THREE.PerspectiveCamera(); this.camera.position.set(0, 0, 0.78); this.control = new THREE.OrbitControls(this.camera, this.dom) this.control.enableDamping = false; this.control.dampingFactor = 0.4; this.control.minDistance = 0.3; this.control.maxDistance = 2; this.control.enablePan = false; this.control.enableZoom = true; this.setRenderer() this.scene = new THREE.Scene; this.pointerDownPos this.textures = []; this.labels = [] this.active = false; this.antialias = true; this.clickTime = new Date().getTime(); this.updateClock = new THREE.Clock; this.init() } Viewer.prototype = Object.create(THREE.EventDispatcher.prototype) Viewer.constructor = Viewer Viewer.prototype.bindEvents = function () { this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown.bind(this), false); this.renderer.domElement.addEventListener('pointerup', this.onPointerUp.bind(this), false); } Viewer.prototype.setRenderer = function () { try { this.renderer = new THREE.WebGLRenderer( { canvas: $(this.dom).find("canvas")[0], antialias: true, alpha: true } ),//许钟文 添加个抗锯齿,否则添加的线条锯齿严重, this.renderer.setClearAlpha(0); //this.renderer.autoClear = !0, this.renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1) // this.renderer.autoClear = false //this.emit(Events.ContextCreated) } catch (e) { console.error("Unable to create a WebGL rendering context") } } Viewer.prototype.update = function (deltaTime) {//绘制的时候同时更新 //if(!this.active)return; this.setSize() this.control.update(deltaTime) transitions.update(deltaTime) var needsUpdate = 1; if (needsUpdate) { //this.renderer.autoClear = true this.renderer.render(this.scene, this.camera) } } Viewer.prototype.hasChanged = function () {//判断画面是否改变了,改变后需要更新一些东西 var copy = function () { this.previousState = { projectionMatrix: this.camera.projectionMatrix.clone(),//worldMatrix在control时归零了所以不用了吧,用position和qua也一样 position: this.camera.position.clone(), quaternion: this.camera.quaternion.clone(), //mouse: this.mouse.clone(), fov: this.camera.fov }; }.bind(this) if (!this.previousState) { copy() return { cameraChanged: !0, changeSlightly: !1 }; } var cameraChanged = !this.camera.projectionMatrix.equals(this.previousState.projectionMatrix) || !this.camera.position.equals(this.previousState.position) || !this.camera.quaternion.equals(this.previousState.quaternion) //var changed = cameraChanged //|| !this.mouse.equals(this.previousState.mouse) let changeSlightly if (cameraChanged) { changeSlightly = math.closeTo(this.camera.position, this.previousState.position, 1e-2) && math.closeTo(this.camera.quaternion, this.previousState.quaternion, 1e-3) } copy() return { cameraChanged, changeSlightly }; } Viewer.prototype.setSize = function () { var w, h, pixelRatio; return function () { if (w != this.dom.clientWidth || h != this.dom.clientHeight || pixelRatio != window.devicePixelRatio) { w = this.dom.clientWidth; h = this.dom.clientHeight; pixelRatio = window.devicePixelRatio; this.camera.aspect = w / h; this.camera.updateProjectionMatrix(); this.renderer.setSize(w, h, false, pixelRatio); } } }() Viewer.prototype.init = function () { this.meshGroup = new THREE.Object3D(); this.scene.add(this.meshGroup); this.meshGroup.name = "viewerMeshGroup"; var buildScene = () => { this.animate() } this.loadOBJ(() => { this.bindEvents() //this.loadLabels() }) buildScene() } let delayNeedUpdated Viewer.prototype.animate = function () { var deltaTime = Math.min(1, this.updateClock.getDelta()); this.update(deltaTime) //bus.emit('player/position/change', {x:this.position.x, y:this.position.z, lon: this.cameraControls.controls.panorama.lon}) let changed = this.hasChanged() if (changed.cameraChanged) { this.dispatchEvent({ type: 'view.changed', changeSlightly: changed.changeSlightly }) delayNeedUpdated = true if( !transitions.funcs.some(function (e) { return e.name === 'cameraFly' })){ convertTool.intervalTool.isWaiting('delayUpdate', ()=>{ //延时update,防止卡顿 if(delayNeedUpdated){ delayNeedUpdated = false this.dispatchEvent({ type: 'delayUpdate' }) console.log('delayUpdate') return true } }, 200) } let label_ = this.labels.filter(e => e.elem[0].style.display == 'block') label_.sort((a, b) => b.pos2d.z - a.pos2d.z) label_.forEach((e, index) => e.elem.css('z-index', index + 1000)); } window.requestAnimationFrame(this.animate.bind(this)); }, Viewer.prototype.loadOBJ = function (done) { var startTime = new Date().getTime(); window.objs = []; var group = new THREE.Object3D; this.meshGroup.add(group); function onProgress(xhr) { if (xhr.lengthComputable) { var percentComplete = xhr.loaded / xhr.total * 100; console.log('model ' + Math.round(percentComplete, 2) + '% downloaded'); } } function onError() { } var MTLLoader = new THREE.MTLLoader(); var OBJLoader = new THREE.OBJLoader() var loadModel = () => { var info = {//凳子 path: 'model/', mtl: 'wl48-he.mtl', obj: 'wl48-he.obj', position: [0, 0], rotation: 0, height: 1 }; if (!info) { console.log("加载持续时间:" + (new Date().getTime() - startTime)) return; } MTLLoader.setPath(info.path).load(info.mtl, (materials) => { materials.preload(); OBJLoader.setMaterials(materials) .setPath(info.path) .load(info.obj, (object) => { group.add(object); object.traverse(function (child) { if (child.isMesh) { console.log(child); if (child.name == "WL48_ping") { let textrueLoader = new THREE.TextureLoader(); var emissiveTexture = textrueLoader.load("model/shadow.jpg"); // emissiveTexture.encoding = THREE.LinearEncoding; child.material.emissiveMap = emissiveTexture; child.material.emissiveIntensity = 0; let step = 1 setInterval(() => { if (window.activetab == 'pic') { if (child.material.emissiveIntensity > 0.3) { step = -1 } if (child.material.emissiveIntensity < 0) { step = 1 } child.material.emissiveIntensity += 0.01 * step; } }, 50); child.material.emissive = new THREE.Color(0xffffff); child.material.dispose(); } /* if(child.geometry){ child.geometry.computeBoundingBox(); bound.union(child.geometry.boundingBox) } */ } }); this.model = object let s = 0.010 object.scale.set(s, s, s) done && done() console.log("加载持续时间:" + (new Date().getTime() - startTime)) setTimeout(() => { this.dispatchEvent({ type: 'hadLoaded' }) }); }, onProgress, onError); }); } loadModel() var light1 = new THREE.AmbientLight(16777215); light1.intensity = 2.8; this.scene.add(light1) var light2 = new THREE.SpotLight(0xffffff, 1); light2.position.set(0, 0, 3) light2.intensity = 0.2; var light3 = new THREE.SpotLight(0xffffff, 1); light3.position.set(0, 0, -3) light3.intensity = 0.4; // let spotLightHelper = new THREE.SpotLightHelper(light2); // let spotLightHelper3 = new THREE.SpotLightHelper(light3); this.scene.add(light2) this.scene.add(light3) // this.scene.add( spotLightHelper ); // this.scene.add( spotLightHelper3 ); } Viewer.prototype.onPointerMove = function (event) { if (event.isPrimary === false) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; if (!this.pointerDownPos) this.checkIntersection(); } Viewer.prototype.onPointerDown = function (event) { if (event.isPrimary === false) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; this.pointerDownPos = mouse.clone() //console.log('onPointerDown') } Viewer.prototype.onPointerUp = function (event) { if (event.isPrimary === false) return; this.dispatchEvent({ type: 'onPointerUp' }) mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; if (this.pointerDownPos && mouse.distanceTo(this.pointerDownPos) < 0.006) {//click this.checkIntersection() // if (this.intersects.length) { // console.log(this.intersects[0].point); // this.addLabel({ position: this.intersects[0].point }) // } // var time = new Date().getTime(); // if (time - this.clickTime < 300) { // if (this.intersects.length) { // console.log('doubleClick'); // transitions.cancelById(0) // transitions.start(lerp.vector(this.control.target, this.intersects[0].point), 600, null, 0/* Delay */, easing.easeInOutQuad, null, Transitions.doubleClick); // } // } // this.clickTime = time; } this.pointerDownPos = null //console.log('onPointerUp') } Viewer.prototype.checkIntersection = function () { raycaster.setFromCamera(mouse, this.camera); const intersects = raycaster.intersectObject(this.model/* this.meshGroup */, true); this.intersects = intersects; } /* Viewer.prototype.adjustModelPos = function(){ //固定地板高度的情况下,调整模型的position.y this.meshGroup.updateMatrixWorld(); this.model.updateMatrixWorld(); var bound = this.model.bound.clone().applyMatrix4(this.model.matrixWorld) var center = bound.getCenter() //为了让最低点在地面上: this.meshGroup.position.y += floorY - bound.min.y //居中: this.meshGroup.position.x += 0 - center.x this.meshGroup.position.z += 0 - center.z } */ Viewer.prototype.removeAllLabels = function (data) { this.labels.forEach(label => { label.dispose() label = null }) this.labels = [] } Viewer.prototype.loadLabelsFromData = function (data) { data.forEach(info => { info.position = new THREE.Vector3().fromArray(info.posInModel).applyMatrix4(this.model.matrixWorld) this.addLabel(info) }) } Viewer.prototype.addLabel = function (o) { labelIndex++ o.title = o.title || ('default' + labelIndex) o.shelterByModel = true let label = new Label2D(o) this.labels.push(label) } Viewer.prototype.removeLabel = function (label) { label.dispose() let index = this.labels.indexOf(label) index > -1 && this.labels.splice(index) label.li && label.li.remove() } Viewer.prototype.setAddLabelState = function (state) { this.addingLabel = !!state $('#addLabel').text(state ? '停止加标签' : '添加标签') } Viewer.prototype.exportLabelData = function () { let data = this.labels.map(label => { let inv = new THREE.Matrix4().getInverse(this.model.matrixWorld) let posInModel = label.position.clone().applyMatrix4(inv) let info = { title: label.title, posInModel: convertTool.toPrecision(posInModel.toArray(), 4), } return info }) $('textarea').css('display', 'block').text(JSON.stringify(data)) console.log(data) return data } Viewer.prototype.getCLabel = function () { //获取最接近中心的label let disSquairs = this.labels.filter(e => e.elem[0].style.display == 'block').map((label) => { return { label, disSquair: label.pos2d.x * label.pos2d.x + label.pos2d.y * label.pos2d.y } }) disSquairs.sort((e1, e2) => { return e1.disSquair - e2.disSquair }) return disSquairs[0] && disSquairs[0].label } //============ var startTime = new Date().getTime(); function dataURLtoBlob(dataurl) {//将base64转换blob var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime }); } /* MeshStandardMaterial(pbr)代替Phong 优点 : 能量守恒、更容易调节出真实感。 metalnessMap只用到一个通道,颜色信息整合到albedo贴图里,省数据。 缺点:可能,mtl里的值只能用到一部分, specular用不到。 (会降低对specular的控制) 另外不知道大部分模型用的是哪种模式,是否使用metalnessMap。 aoMap r roughnessMap alphaMap g metalnessMap b normalMap ? bumpMap : 黑白? 主要是光滑度or粗糙度(可贴图) 还有 金属性(可贴图) 还有颜色(可贴图),透明度(in颜色贴图),法线贴图or凹凸贴图 贴图的属性 rotation offset repeat (当wrapS = THREE.RepeatWrapping,wrapT = THREE.RepeatWrapping) */