/** * @Documents * * 配置3D时选择模型文件后使用 MDCThreeD.readCabinetList 读取模型文件中的柜子列表,返回所有柜子的编号 * MDCThreeD.readCabinetList('./img/3d/molds/mdc.fbx').then(indexs => { * console.log(indexs); * }); */ /** * 3D页面 * @param {*} options 初始化选项 * @returns */ function MDCThreeD(options) { this.options = options; this.container = options.container; this.touchHandle = this.onTouch.bind(this); this.touchendHandle = this.onTouchEnd.bind(this); this.touchMoveHandle = this.onTouchMove.bind(this); this.mousemoveHandle = this.onMouseMove.bind(this); this.mouseleaveHandle = this.onMouseLeave.bind(this); this.scope = options.scope; this.exitFlag = false; if (this.options.buttons) { for (var i = 0; i < this.options.buttons.length; i++) { var self = this; this.options.buttons[i].index = i; this.options.buttons[i].onclick = function (arg) { var index = arg[0]; var position = self.originPosition; if (index === 0) { if (self.camera.position.distanceTo(self.originPosition) < 1) { position = self.getAnotherSide(self.originPosition); } } self.camera.position.copy(position); self.camera.updateProjectionMatrix(); for (var i = 0; i < self.options.buttons.length; i++) { self.options.buttons[i].classList.remove('mdc-3d-button-active'); } self.options.buttons[index].classList.add('mdc-3d-button-active'); for (var i = 0; i < self.actions.length; i++) { self.actions[i].stop(); } self.controls.autoRotate = index === 1; if (index === 2) { for (var i = 0; i < self.actions.length; i++) { self.actions[i].play(); } } }.bind(this, [i]) } } if (this.isWebGL2Available()) { this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); } else if (this.isWebGLAvailable()) { this.renderer = new THREE.WebGL1Renderer({ antialias: true, alpha: true }); } else { throw new Error('该浏览器不支持WEBGL3D渲染,请尝试使用最新版本的Chrome或Edge浏览器。'); } if (typeof this.renderer.domElement.onpointerdown != 'undefined') { this.renderer.domElement.addEventListener('pointerdown', this.touchHandle, false); } else { this.renderer.domElement.addEventListener('mousedown', this.touchHandle, false); } if (typeof this.renderer.domElement.onpointerup != 'undefined') { this.renderer.domElement.addEventListener('pointerup', this.touchendHandle, false); } else { this.renderer.domElement.addEventListener('mouseup', this.touchendHandle, false); } if (typeof this.renderer.domElement.onpointermove != 'undefined') { this.renderer.domElement.addEventListener('pointermove', this.touchMoveHandle, false); } else { this.renderer.domElement.addEventListener('mousemove', this.touchMoveHandle, false); } // 鼠标停留 this.renderer.domElement.addEventListener('mousemove', this.mousemoveHandle, false); // 鼠标离开 this.renderer.domElement.addEventListener('mouseleave', this.mouseleaveHandle, false); // this.renderer.domElement.addEventListener('touchstart', this.touchHandle, false); // this.renderer.domElement.addEventListener('touchmove', this.touchMoveHandle, false); // this.renderer.domElement.addEventListener('touchend', this.touchendHandle, false); this.container.appendChild(this.renderer.domElement); this.renderer.domElement.style.width = '100%'; this.renderer.domElement.style.height = '100%'; this.initData(); this.loadCamera(); this.loadControls(); this.loadEnvironment(); this.loadTemplate(); requestAnimationFrame(this.render.bind(this)); return this; } /** * 定义柜子对象的命名规则 */ MDCThreeD.cabinetRegExp = /cabinet[1-9]+[0-9]*/; /*:1 * 机柜对象表 */ MDCThreeD.prototype.cabinets = {}; /** * 3D配置选项 */ MDCThreeD.prototype.options = { /** * 容器DIV对象,初始化传入\ */ container: null, /** * 静态背景图路径,可选\ */ background: null, /** * 控制按钮列表\ * 传入button对象数组 * [0:静止,1:旋转,2:动画] */ buttons: [], /** * 是否使用环境光贴图?\ * 模拟房间的环境光 */ environmentLight: true, /** * 模型文件 */ modelFile: './img/3d/molds/mdc.fbx', /** * 机柜点击变色 \ * #007ACC */ touchDiscolor: null } /** * 初始化数据 */ MDCThreeD.prototype.initData = function () { this.cabinets = {}; this.actions = []; this.touchObject = null; this.mixer = new THREE.AnimationMixer(); this.clock = new THREE.Clock(); this.scene = new THREE.Scene(); this.raycaster = new THREE.Raycaster(); } /** * 命中对象事件 * @param {*} object 命中对象 * @param {*} cabinetIndex 对象的柜子编号 */ MDCThreeD.prototype.objectTouch = function (object, cabinetIndex) { this.scope.cabinetClk(cabinetIndex); } /** * 获取一个坐标围绕中心点的另一面 * Y轴旋转180度 * @param {*} position * @returns */ MDCThreeD.prototype.getAnotherSide = function (position) { var ps = position.clone().sub(this.controls.target); this.transformGroup.rotation.set(0, 0, 0); this.transformObject.position.copy(ps); this.transformGroup.rotation.y = Math.PI; this.transformGroup.updateMatrixWorld(true); const pos = new THREE.Vector3(); pos.setFromMatrixPosition(this.transformObject.matrixWorld) return pos.add(this.controls.target); } /** * 鼠标按下事件处理 * @param {*} event */ MDCThreeD.prototype.onTouch = function (event) { event.preventDefault(); const position = this.getPlatformMousePosition(event); var rect = this.renderer.domElement.getBoundingClientRect(); position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top)); var x = (position.x / rect.width) * 2 - 1; var y = -(position.y / rect.height) * 2 + 1; this.coord = new THREE.Vector2(x, y); this.raycaster.setFromCamera(this.coord, this.camera); var hitobjects = this.raycaster.intersectObjects(this.scene.children, true); if (hitobjects.length > 0) { const object = hitobjects[0].object; if (this.isCabinet(object)) { this.touchPosition = position; this.touchObject = object; this.discolor(this.touchObject, true); } } } /** * 鼠标停留 * @Author: Eddy * @Date: 2021-06-05 16:59:25 */ MDCThreeD.prototype.onMouseMove = function (event) { event.preventDefault(); const position = this.getPlatformMousePosition(event); var rect = this.renderer.domElement.getBoundingClientRect(); position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top)); var x = (position.x / rect.width) * 2 - 1; var y = -(position.y / rect.height) * 2 + 1; this.coord = new THREE.Vector2(x, y); this.raycaster.setFromCamera(this.coord, this.camera); var hitobjects = this.raycaster.intersectObjects(this.scene.children, true); var currentObject = null; if (hitobjects.length > 0) { const object = hitobjects[0].object; if (this.isCabinet(object)) { currentObject = object; } } if (this.nouseObject != currentObject) { if (this.nouseObject != null) { this.discolor(this.nouseObject, false); this.nouseObject = null; } this.nouseObject = currentObject; if (this.nouseObject != null) { this.discolor(this.nouseObject, true); } } } /** * 鼠标离开画布 * @Author: Eddy * @Date: 2021-06-05 17:02:45 */ MDCThreeD.prototype.onMouseLeave = function (event) { if (this.nouseObject) { this.discolor(this.nouseObject, false); } } /** * 鼠标移动事件 * @param {*} event */ MDCThreeD.prototype.onTouchMove = function (event) { if (this.touchObject && this.touchPosition) { const position = this.getPlatformMousePosition(event); var rect = this.renderer.domElement.getBoundingClientRect(); position.sub(new THREE.Vector2(rect.x | rect.left, rect.y | rect.top)); /* 按下后拖动距离大于5像素,取消点击处理 */ const distance = this.touchPosition.clone().distanceTo(position.clone()); if (distance > 5) { this.discolor(this.touchObject, false); this.touchPosition = null; this.touchObject = null; } event.preventDefault(); } } /** * 鼠标抬起事件 * @param {*} event * @returns */ MDCThreeD.prototype.onTouchEnd = function (event) { if (this.touchPosition == null || this.touchObject == null) return; event.preventDefault(); this.discolor(this.touchObject, false); this.objectTouch(this.touchObject, this.touchObject.userData.cabinetIndex); this.touchPosition = null; this.touchObject = null; } /** * 变色 * @param {*} object * @param {*} enabled * @returns */ MDCThreeD.prototype.discolor = function (object, enabled) { if (this.options.touchDiscolor == null) return; var emissive = new THREE.Color(this.options.touchDiscolor); if (enabled) { if (object._material == null) { object._material = object.material; if (object._material instanceof THREE.Material) { object.material = object.material.clone(); object.material.emissive = emissive; } else if (object.material instanceof Array) { const materials = []; for (var i = 0; i < object.material.length; i++) { const m = object.material[i].clone(); m.emissive = emissive; materials.push(m); } object.material = materials; } } } else { if (object._material) { if (object.material instanceof THREE.Material) { object.material.dispose(); } else if (object.material instanceof Array) { for (var i = 0; i < object.material.length; i++) { object.material[i].dispose(); } object.material.length = 0; } object.material = object._material; delete object._material; } } } MDCThreeD.prototype.loadObjectLabels = function () { var scope = this.options.scope; var devices = scope.NewMdcConfigures; var configs = scope.cabinets; for (var key in this.cabinets) { var cabinet = this.cabinets[key]; var object3d = cabinet.object; var index = cabinet.index; var txtobject = object3d.getObjectByName('txt' + index); if (txtobject) { var info = this.createCanvasMaterial(this.options.labelOptions.width, this.options.labelOptions.height); var cfg = _.findWhere(configs, { no: index }); if (cfg) { this.drawCanvasLabel(info, cfg.cabinetName); // var dev = _.findWhere(devices, { // configId: cfg.configId // }); // if (dev) { // this.drawCanvasLabel(info, dev.configName); // } } //this.drawCanvasLabel(info, "Txt"+index); txtobject.material = info.material; }else console.log("3D模型不正确!"); } } /** * 渲染标签文本 * @param {*} info 材质纹理等信息 * @param {*} text 渲染文本 */ MDCThreeD.prototype.drawCanvasLabel = function (info, text) { var options = this.options.labelOptions; var ctx = info.context; var fontsize = options.fontSize == null ? info.height * 0.8 : options.fontSize; var fontstyle = fontsize + 'px ' + options.font; if (options.bold) fontstyle += " bold"; ctx.font = fontstyle; ctx.clearRect(0, 0, info.width, info.height); ctx.fillStyle = options.color; ctx.globalAlpha = 1; ctx.textBaseline = "top"; var textwidth = ctx.measureText(text).width; var left = (info.width - textwidth) / 2; var top = (info.height - fontsize) / 2; ctx.fillText(text, left, top); info.texture.needsUpdate = true; } /** * 创建材质和贴图 * @param {*} width * @param {*} height * @returns */ MDCThreeD.prototype.createCanvasMaterial = function (width, height) { var canvas = document.createElement('canvas'); var devicePixelRatio = window.devicePixelRatio; canvas.style.width = width + "px"; canvas.style.height = height + "px"; canvas.height = height * devicePixelRatio; canvas.width = width * devicePixelRatio; var context = canvas.getContext('2d'); context.scale(devicePixelRatio, devicePixelRatio); context.imageSmoothingEnabled = true; context.translate(0, 0); context.imageSmoothingEnabled = true; context.imageSmoothingQuality = "high"; context.clearRect(0, 0, width, height); var texture = new THREE.CanvasTexture(canvas); // texture.encoding = THREE.sRGBEncoding; texture.magFilter = THREE.NearestFilter; texture.minFilter = THREE.LinearMipmapLinearFilter; var material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest : false }); return { width: width, height: height, canvas: canvas, context: context, texture: texture, material: material } } /** * 加载模型 */ MDCThreeD.prototype.loadTemplate = function () { const loader = new THREE.FBXLoader(); var self = this; loader.load(this.options.modelFile, function (root) { root.position.set(0, 0, 0); root.traverse(function (e) { if (e instanceof THREE.Light) { e.intensity = 0.5; } self.initCabinet(e); }); self.scene.add(root); root.updateMatrixWorld(); // set the camera to frame the box self.frameArea(root, 0.9); // update the Trackball controls to handle the new size if (root.animations && root.animations.length > 0) { for (var i = 0; i < root.animations.length; i++) { const action = self.mixer.clipAction(root.animations[i], root); action.loop = THREE.LoopRepeat; self.actions.push(action); } } self.loadObjectLabels(); }); } /** * [静态方法]\ * 从模型文件读取柜子的列表 * @param {string} modelFile 模型文件路径 */ // MDCThreeD.readCabinetList = async function (modelFile) { // const result = []; // const loader = new THREE.FBXLoader(); // const root = await loader.loadAsync(modelFile); // root.traverse(function (object) { // if (object instanceof THREE.Mesh) { // const indexs = object.name.match(/\d+(\.\d+)?/g); // if (MDCThreeD.cabinetRegExp.test(object.name) && indexs.length === 1) { // const index = Number.parseInt(indexs[0]); // result.push(index); // } // } // }); // return result.sort(function (a, b) { // return a < b ? -1 : (a === b) ? 0 : 1 // }); // } /** * 初始化柜子 * @param {*} object * @returns */ MDCThreeD.prototype.initCabinet = function (object) { if (object instanceof THREE.Mesh) { const indexs = object.name.match(/\d+(\.\d+)?/g); if (MDCThreeD.cabinetRegExp.test(object.name) && indexs.length === 1) { const index = parseInt(indexs[0]); object.userData.selectable = true; object.userData.cabinetIndex = index; this.cabinets[index] = { index: index, object: object, }; } } } /** * 判断命中对象是否是柜子 * @param {*} object * @returns */ MDCThreeD.prototype.isCabinet = function (object) { return object.userData.selectable && object.userData.cabinetIndex; } MDCThreeD.prototype.dispose = function () { this.exitFlag = true; this.renderer.domElement.removeEventListener('pointerdown', this.touchHandle); this.renderer.domElement.removeEventListener('pointermove', this.touchMoveHandle); this.renderer.domElement.removeEventListener('pointerup', this.touchendHandle); this.renderer.domElement.removeEventListener('touchstart', this.touchHandle); this.renderer.domElement.removeEventListener('touchmove', this.touchMoveHandle); this.renderer.domElement.removeEventListener('touchend', this.touchendHandle); if (this.renderer) { this.scene.traverse(function (e) { if (e instanceof THREE.Mesh) { if (e.geometry) e.geometry.dispose(); if (e.material instanceof Array) { for (var i = 0; i < e.material.length; i++) { e.material[i].dispose(); } } else if (e.material) e.material.dispose(); } }); this.renderer.dispose(); this.renderer = null; } }; MDCThreeD.prototype.render = function () { if (this.exitFlag) return; this.controls.update(); var delta = this.clock.getDelta(); this.mixer.update(delta); if (this.resizeRendererToDisplaySize()) { const canvas = this.renderer.domElement; this.camera.aspect = canvas.clientWidth / canvas.clientHeight; this.camera.updateProjectionMatrix(); } this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.render.bind(this)); }; MDCThreeD.prototype.isWebGLAvailable = function () { try { var canvas = document.createElement('canvas'); return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); } catch (e) { return false; } }; MDCThreeD.prototype.getPlatformMousePosition = function (event) { if (event.touches != null && event.touches.length > 0) { return new THREE.Vector2(event.touches[0].clientX, event.touches[0].clientY); } else { var u = window.navigator.userAgent.toLocaleLowerCase(); var b = u.match(/(trident)\/([\d.]+)/); if (b) { var rect = event.target.getBoundingClientRect(); return new THREE.Vector2(event.pageX - rect.left, event.pageY - rect.top); } else { return new THREE.Vector2(event.clientX, event.clientY); } } } MDCThreeD.prototype.isWebGL2Available = function () { try { var canvas = document.createElement('canvas'); return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2')); } catch (e) { return false; } }; MDCThreeD.prototype.resizeRendererToDisplaySize = function () { const canvas = this.renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { this.camera.aspect = width / height; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height, false); } return needResize; }; MDCThreeD.prototype.frameArea = function (object, sizeToFitOnScreen) { const box = new THREE.Box3().setFromObject(object); const size = box.getSize(new THREE.Vector3()); const boxSize = size.length(); const boxCenter = box.getCenter(new THREE.Vector3()); boxCenter.setY(-size.y / 2); //起始位置 const halfSizeToFitOnScreen = boxSize * sizeToFitOnScreen * 0.5; const halfFovY = THREE.MathUtils.degToRad(this.camera.fov * .5); const distance = halfSizeToFitOnScreen / Math.tan(halfFovY); const direction = (new THREE.Vector3()) .subVectors(this.camera.position, boxCenter) .multiply(new THREE.Vector3(1, 0, 1)) .normalize(); this.camera.position.copy(direction.multiplyScalar(distance).add(boxCenter)); this.camera.near = boxSize / 100; this.camera.far = boxSize * 100; this.camera.position.setX(1300); this.camera.position.setY(700); this.camera.position.setZ(300); this.originPosition = this.camera.position.clone(); this.camera.updateProjectionMatrix(); this.camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z); this.controls.maxDistance = boxSize * 10; this.controls.target.copy(boxCenter); this.controls.update(); }; MDCThreeD.prototype.RoomEnvironment = function () { const scene = new THREE.Scene(); const geometry = new THREE.BoxBufferGeometry(); geometry.deleteAttribute('uv'); const roomMaterial = new THREE.MeshStandardMaterial({ side: THREE.BackSide }); const boxMaterial = new THREE.MeshStandardMaterial(); const mainLight = new THREE.PointLight(0xffffff, 5.0, 28, 2); mainLight.position.set(0.418, 16.199, 0.300); scene.add(mainLight); const room = new THREE.Mesh(geometry, roomMaterial); room.position.set(-0.757, 13.219, 0.717); room.scale.set(31.713, 28.305, 28.591); scene.add(room); const box1 = new THREE.Mesh(geometry, boxMaterial); box1.position.set(-10.906, 2.009, 1.846); box1.rotation.set(0, -0.195, 0); box1.scale.set(2.328, 7.905, 4.651); scene.add(box1); const box2 = new THREE.Mesh(geometry, boxMaterial); box2.position.set(-5.607, -0.754, -0.758); box2.rotation.set(0, 0.994, 0); box2.scale.set(1.970, 1.534, 3.955); scene.add(box2); const box3 = new THREE.Mesh(geometry, boxMaterial); box3.position.set(6.167, 0.857, 7.803); box3.rotation.set(0, 0.561, 0); box3.scale.set(3.927, 6.285, 3.687); scene.add(box3); const box4 = new THREE.Mesh(geometry, boxMaterial); box4.position.set(-2.017, 0.018, 6.124); box4.rotation.set(0, 0.333, 0); box4.scale.set(2.002, 4.566, 2.064); scene.add(box4); const box5 = new THREE.Mesh(geometry, boxMaterial); box5.position.set(2.291, -0.756, -2.621); box5.rotation.set(0, -0.286, 0); box5.scale.set(1.546, 1.552, 1.496); scene.add(box5); const box6 = new THREE.Mesh(geometry, boxMaterial); box6.position.set(-2.193, -0.369, -5.547); box6.rotation.set(0, 0.516, 0); box6.scale.set(3.875, 3.487, 2.986); scene.add(box6); // -x right const light1 = new THREE.Mesh(geometry, createAreaLightMaterial(50)); light1.position.set(-16.116, 14.37, 8.208); light1.scale.set(0.1, 2.428, 2.739); scene.add(light1); // -x left const light2 = new THREE.Mesh(geometry, createAreaLightMaterial(50)); light2.position.set(-16.109, 18.021, -8.207); light2.scale.set(0.1, 2.425, 2.751); scene.add(light2); // +x const light3 = new THREE.Mesh(geometry, createAreaLightMaterial(17)); light3.position.set(14.904, 12.198, -1.832); light3.scale.set(0.15, 4.265, 6.331); scene.add(light3); // +z const light4 = new THREE.Mesh(geometry, createAreaLightMaterial(43)); light4.position.set(-0.462, 8.89, 14.520); light4.scale.set(4.38, 5.441, 0.088); scene.add(light4); // -z const light5 = new THREE.Mesh(geometry, createAreaLightMaterial(20)); light5.position.set(3.235, 11.486, -12.541); light5.scale.set(2.5, 2.0, 0.1); scene.add(light5); // +y const light6 = new THREE.Mesh(geometry, createAreaLightMaterial(100)); light6.position.set(0.0, 20.0, 0.0); light6.scale.set(1.0, 0.1, 1.0); scene.add(light6); function createAreaLightMaterial(intensity) { const material = new THREE.MeshBasicMaterial(); material.color.setScalar(intensity); return material; } return scene; } MDCThreeD.prototype.loadCamera = function () { const fov = 45; const aspect = 2; // the canvas default const near = 0.1; const far = 100; this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far); this.camera.position.set(1500, 700, 500); } MDCThreeD.prototype.loadControls = function () { this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.target.set(0, 5, 0); this.controls.enablePan = false; this.controls.enableDamping = false; this.controls.minPolarAngle = 0; this.controls.maxPolarAngle = Math.PI * 0.5; this.controls.autoRotate = false; this.controls.update(); } MDCThreeD.prototype.loadEnvironment = function () { // if (this.options.environmentLight) { // const pmremGenerator = new THREE.PMREMGenerator(this.renderer); // const texture = pmremGenerator.fromScene(this.RoomEnvironment()).texture; // this.scene.environment = texture; // pmremGenerator.dispose(); // } if (this.options.background) { this.scene.background = new THREE.TextureLoader().load(this.options.background); } // const light = new THREE.AmbientLight(0xFFFFFF, 0.2); this.scene.add(light); // const skyColor = 0xB1E1FF; const groundColor = 0xB97A20; const intensity = 0.5; const hemisophereLight = new THREE.HemisphereLight(skyColor, groundColor, intensity); this.scene.add(hemisophereLight); /* 旋转变换 */ this.transformGroup = new THREE.Object3D(); this.transformObject = new THREE.Object3D(); this.transformGroup.add(this.transformObject); }