import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { DragControls } from 'three/addons/controls/DragControls.js'; import Stats from 'three/addons/libs/stats.module.js'; import { OBJLoader } from 'three/addons/loaders/OBJLoader.js'; import { TransformControls } from 'three/addons/controls/TransformControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js' import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js' export default class Scene3d { selectObject; constructor(_element) { this._initScene(_element); } /** * 初始化场景 */ initScene() { this.scene = new THREE.Scene(); //this.scene.fog = new THREE.Fog(0x111111, 150, 200); // show axes in the screen let axes = new THREE.AxesHelper(5); this.scene.add(axes); let helper = new THREE.GridHelper(50, 20, 0xCD3700, 0x4A4A4A);//网格线 this.scene.add(helper); } /** * 初始化相机 */ initCamera() { this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // position and point the camera to the center of the scene this.camera.position.set(-30, 40, 30); this.camera.lookAt(this.scene.position); } /** * 初始化渲染器 */ initRenderer(_element) { // create a render and set the size this.renderer = new THREE.WebGLRenderer(); this.renderer.setClearColor(new THREE.Color(0x87CEEB)); this.renderer.setSize(window.innerWidth - 390, window.innerHeight); this.renderer.antialias = true; this.renderer.alpha = true; this.renderer.precision = 'mediump' // add the output of the renderer to the html element this.container = document.getElementById(_element); this.container.appendChild(this.renderer.domElement); } /** * 性能检测 */ initStats() { this.stats = new Stats(); this.stats.domElement.style.position = 'absolute'; this.stats.domElement.style.left = '200px'; this.container.appendChild(this.stats.dom); } initControls() { this.controls = new OrbitControls(this.camera, this.renderer.domElement); // 设置控制器阻尼,让控制器更有真是效果,必须在动画循环里调用update() //this.controls.enableDamping = true; this.controls.minPolarAngle = 0; this.controls.maxPolarAngle = 75 / 180 * Math.PI; } /** * 初始化灯光 */ initLight() { this.light = new THREE.SpotLight(0xffffff); this.light.position.set(-300, 600, -400); this.light.castShadow = true; this.scene.add(this.light); this.scene.add(new THREE.AmbientLight(0x404040)); } update() { this.controls.update(); this.stats.update(); } changeMaterial(object) { let material = new THREE.MeshLambertMaterial({ color: 0xffffff * Math.random(), transparent: object.material.transparent ? false : true, opacity: 0.8 }); object.material = material; } onMouseDblclick(event, _that) { //alert("a"); //获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前 let intersects = _that.getIntersects(event, _that); console.log(intersects); // console.log(intersects[0].object); //获取选中最近的Mesh对象 //instance坐标是对象,右边是类,判断对象是不是属于这个类的 let _name = ''; //&& intersects[0].object.type === 'Mesh' if (intersects.length !== 0) { //intersects[0].object.material.color.set(0x00FF00); //console.log(intersects[0].object.material.color); for (let _index = 0; _index < intersects.length; _index++) { _name = intersects[_index].object.name; if (_name == 'X' || _name == 'Y' || _name == 'Z' || _name == 'XY' || _name == 'XZ' || _name == 'YZ' || _name == 'XYZ' || _name == 'E' || _name == 'XYZE' || _name == 'START' || _name == 'END') { continue } if (intersects[_index].object.type === 'Mesh') { _that.selectObject = intersects[_index].object; if (_that.selectCallBack) { _that.selectCallBack(_that.selectObject); } break; } } //changeMaterial(selectObject) } else { console.log('未选中 Mesh!'); } } //获取与射线相交的对象数组 getIntersects(event, _that) { console.log('target = ',event.target.tagName ); if(event.clientX < 190){ return []; } if(event.clientX > window.innerWidth - 200){ return []; } if(event.target.tagName != 'CANVAS'){ return []; } event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault // console.log("event.clientX:" + event.clientX); // console.log("event.clientY:" + event.clientY); //声明 rayCaster 和 mouse 变量 let rayCaster = new THREE.Raycaster(); let mouse = new THREE.Vector2(); //通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1 mouse.x = ((event.clientX - _that.container.getBoundingClientRect().left) / _that.container.offsetWidth) * 2 - 1; mouse.y = -((event.clientY - _that.container.getBoundingClientRect().top) / _that.container.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中 //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置 rayCaster.setFromCamera(mouse, _that.camera); //获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。 //+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。 let intersects = rayCaster.intersectObjects(_that.scene.children, true); //返回选中的对象 return intersects; } // 窗口变动触发的方法 onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } //键盘按下触发的方法 onKeyDown(event, _that) { switch (event.keyCode) { case 13: this.initCamera(); this.initControls(); break; case 87: // W _that.transformControls.setMode('translate'); break; case 69: // E _that.transformControls.setMode('rotate'); break; case 82: // R _that.transformControls.setMode('scale'); break; } } _initScene(_element) { this.initScene(); this.initCamera(); this.initRenderer(_element); this.initStats(); this.initLight(); this.initControls(); this.transformControls = new TransformControls(this.camera, this.renderer.domElement); this.scene.add(this.transformControls); let _that = this; this.transformControls.addEventListener('dragging-changed', function (event) { _that.controls.enabled = !event.value; }); addEventListener('click', function (event) { _that.onMouseDblclick(event, _that) }, false); //addEventListener('resize', onWindowResize, false); addEventListener('keydown', function (event) { _that.onKeyDown(event, _that); }, false); function animate() { // if (selectObject != undefined && selectObject != null) { // //renderDiv(selectObject); // } requestAnimationFrame(animate); _that.renderer.render(_that.scene, _that.camera); _that.update(_that); //判断渲染器是否调整,若调整,相机也需要调整aspect // if (resizeRendererToDisplaySize(renderer)) { // const canvas = renderer.domElement; // //重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形 // camera.aspect = canvas.clientWidth / canvas.clientHeight; // camera.updateProjectionMatrix(); // } } animate(); } setSelect3DObject(_callback) { this.selectCallBack = _callback; } setTransformControlModal(_modal) { if (!_modal) { _modal = 'translate'; } this.transformControls.setMode(_modal); } controlObject(_object) { this.transformControls.attach(_object) } /** * 加载,模型 * @param {模型信息} _modal */ add3DObject(_modal) { const loader = new OBJLoader(); let _that = this; loader.load( // resource URL _modal.path, // called when resource is loaded function (object) { // let material = new THREE.MeshBasicMaterial({}); // //obj模型是有无数个mesh子元素组成,所以我们可以对单个child的mesh进行操作 // object.children.forEach(function (child) { // child.material = material; // // child.material=new THREE.MeshBasicMaterial({map:new THREE.TextureLoader().load('./statics/imgs/floor.jpg'),side:THREE.DoubleSide}); // child.geometry.computeFaceNormals(); // child.geometry.computeVertexNormals(); // }); object.scale.set(1, 1, 1); _that.scene.add(object); }, // called when loading is in progresses function (xhr) { console.log((xhr.loaded / xhr.total * 100) + '% loaded'); }, // called when loading has errors function (error) { console.log('An error happened', error); } ); } addGltfObject(_modal) { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('/js/three/examples/jsm/libs/draco/'); loader.setDRACOLoader(dracoLoader); let _that = this; loader.load( // resource URL _modal.path, // called when the resource is loaded function (gltf) { //需要添加的部分 gltf.scene.traverse(function (child) { if (child.isMesh) { child.material.emissive = child.material.color; child.material.emissiveMap = child.material.map; } }); _that.scene.add(gltf.scene); //_that.scene.add(_that.merge(gltf.scene)); // gltf.animations; // Array // gltf.scene; // THREE.Group // gltf.scenes; // Array // gltf.cameras; // Array // gltf.asset; // Object }, // called while loading is progressing function (xhr) { console.log((xhr.loaded / xhr.total * 100) + '% loaded'); }, // called when loading has errors function (error) { console.log('An error happened', error); } ); } addJpgBackgroup(_modal) { const texture = new THREE.TextureLoader().load(_modal.path); texture.mapping = THREE.EquirectangularReflectionMapping; texture.magFilter = THREE.LinearFilter;//③ texture.minFilter = THREE.LinearMipmapLinearFilter;//④ texture.encoding = THREE.sRGBEncoding; //texture.repeat.set( 4, 4 ); this.scene.background = texture; this.scene.environment = texture; } addHdrBackgroup(_modal) { const rgbeLoader = new RGBELoader(); //资源较大,使用异步加载 let _that = this; rgbeLoader.loadAsync(_modal.path).then((texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; //将加载的材质texture设置给背景和环境 _that.scene.background = texture; _that.scene.environment = texture; }); } merge(object) { const geometries = []; const materialArr = []; object.traverse((obj) => { const child = obj; if (child.isMesh) { const geo = child.geometry.clone(); if (Array.isArray(child.material)) { child.material = child.material[0]; } materialArr.push(child.material); geo.index = null; child.updateWorldMatrix(true, true); geo.applyMatrix4(child.matrixWorld); geometries.push(geo); } }); // console.log(geometries); const buffergeo = BufferGeometryUtils.mergeBufferGeometries( geometries, true ); // console.log(buffergeo); const mesh = new THREE.Mesh(buffergeo, materialArr); return mesh; } add3DBox(_modal) { const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); this.scene.add(cube); } deleteObject(_object) { this.transformControls.detach(); _object.removeFromParent(); // if (_object) { // _object.traverse(function (item) { // if (item instanceof THREE.Mesh) { // if (Array.isArray(item.material)) { // item.material.forEach(a => { // a.dispose() // }) // } else { // item.material.dispose() // 删除材质 // } // item.geometry.dispose() // 删除几何体 // } // item = null // }) // // 删除场景对象scene的子对象group // this.scene.remove(_object); // } } getObjectTree() { let _children = this.scene.children; let _objects = []; _children.forEach(_item => { if (_item instanceof THREE.AxesHelper) { return; } if (_item instanceof THREE.GridHelper) { return; } if (_item instanceof THREE.SpotLight) { return; } if (_item instanceof THREE.AmbientLight) { return; } if (_item instanceof TransformControls) { return; } _objects.push(_item) }) return _objects; } getAllObject() { let _objects = []; let _name = ''; this.scene.traverse(function (child) { if (child instanceof THREE.Mesh) { _name = child.name; if (_name == 'X' || _name == 'Y' || _name == 'Z' || _name == 'XY' || _name == 'XZ' || _name == 'YZ' || _name == 'XYZ' || _name == 'E' || _name == 'XYZE' || _name == 'START' || _name == 'END') { return; } _objects.push(child); } }); return _objects; } getObject(_uuid) { return this.scene.getObjectById(_uuid); } exportScene() { return JSON.stringify(this.scene.toJSON()); } /** * gltf和glb导出器 * @param option */ exporterGlb() { const gltfExporter = new GLTFExporter(); const options = { trs: false, //导出位置、旋转和缩放,而不是每个节点的矩阵 默认是false onlyVisible: true, //只导出可见的对象 默认是true truncateDrawRange: true, binary: true, //binary==true 导出glb | binary==false 导出gltf forceindices: false, //为非索引几何图形生成索引并导出它们 默认false maxTextureSize: 4096, //限制图像的最大尺寸(宽度、高度)为给定值。默认是无限 }; let _children = this.scene.children; let _objects = []; _children.forEach(_item => { if (_item instanceof THREE.AxesHelper) { return; } if (_item instanceof THREE.GridHelper) { return; } if (_item instanceof THREE.SpotLight) { return; } if (_item instanceof THREE.AmbientLight) { return; } if (_item instanceof TransformControls) { return; } _objects.push(_item) }) return new Promise((resolve, reject) => { gltfExporter.parse( _objects, //这是个数组 function (result) { let _data = ''; //if (result instanceof ArrayBuffer) { _data = new Blob([result], { type: 'application/octet-stream' }, 'hc_scene.glb'); // } else { // const output = JSON.stringify(result, null, 2); // save(new Blob([output], { type: 'text/plain' }), 'scene.gltf'); // } resolve(_data) }, function (error) { console.error(err); reject(error) }, options ); }) } }