import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; import { FontLoader } from 'three/addons/loaders/FontLoader.js'; export default class Cockpit3d { constructor(_element) { this._initScene(_element); this.shapes = []; } /** * 初始化场景 */ initScene() { this.scene = new THREE.Scene(); } /** * 初始化相机 */ 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, 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); } 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; this.controls.minDistance = 200; this.controls.maxDistance = 500; // 相机位置与观察目标点最大值 } /** * 初始化灯光 */ 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.upDownShap(); } _initScene(_element) { this.initScene(); this.initCamera(); this.initRenderer(_element); this.initLight(); this.initControls(); let _that = this; function animate() { requestAnimationFrame(animate); _that.renderer.render(_that.scene, _that.camera); _that.update(_that); } animate(); addEventListener('click', function (event) { _that.onMouseDblclick(event, _that) }, false); window.addEventListener('mousemove', function (event) { _that.onMouseMove(event, _that) }, false); } addGltfObject(_modal) { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('/js/three/examples/jsm/libs/draco/'); loader.setDRACOLoader(dracoLoader); let _that = this; return new Promise((resolve, reject) => { loader.load( _modal.path, function (gltf) { //需要添加的部分 gltf.scene.traverse(function (child) { if (child.isMesh) { child.material.emissive = child.material.color; child.material.emissiveMap = child.material.map; } }); gltf.scene.scale.set(1, 1, 1); _that.scene.add(gltf.scene); resolve(); }, // 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); reject(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; }); } clearThree(obj) { while (obj.children.length > 0) { this.clearThree(obj.children[0]) obj.remove(obj.children[0]); } if (obj.geometry) obj.geometry.dispose() if (obj.material) { //in case of map, bumpMap, normalMap, envMap ... Object.keys(obj.material).forEach(prop => { if (!obj.material[prop]) return if (typeof obj.material[prop].dispose === 'function') obj.material[prop].dispose() }) obj.material.dispose() } } resetScene() { this.clearThree(this.scene); } setSelect3DObject(_callback) { this.selectCallBack = _callback; } onMouseMove(event, _that) { let intersects = _that.getIntersects(event, _that); if (!intersects || intersects.length === 0) { return; } let _name = ""; document.body.style.cursor = 'default'; for (let _index = 0; _index < intersects.length; _index++) { _name = intersects[_index].object.name; if (_name.endsWith('_focus') || _name.endsWith('_text')) { document.body.style.cursor = 'pointer'; // 将鼠标状态设置为小手 break; } } } onMouseDblclick(event, _that) { //alert("a"); //获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前 let intersects = _that.getIntersects(event, _that); // 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) { 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; } getObjectByName(_objName) { return this.scene.getObjectByName(_objName); } addConicalShape(_objName) { let geometry = new THREE.ConeGeometry(8, 15, 20); // 第三个参数是分段数,可以根据需要调整 // 创建材质 let material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.7 }); // 绿色 // 创建锥形对象 let cone = new THREE.Mesh(geometry, material); cone.name = _objName; cone.rotation.x = Math.PI; this.scene.add(cone); // 将锥形对象添加到场景中 } addText(_objName, _text, _position) { let _that = this; const loader = new FontLoader(); loader.load('/js/three/examples/fonts/helvetiker_regular.typeface.json', function (font) { const geometry = new TextGeometry(_text, { font: font, size: 6, height: 1, curveSegments: 8, bevelEnabled: true, bevelThickness: 1, bevelSize: 0.1, bevelSegments: 2 }); let material = new THREE.MeshBasicMaterial({ color: 0xc17e14, transparent: true, opacity: 0.8}); // 创建材质 let mesh = new THREE.Mesh(geometry, material); // 创建网格并添加到场景中 mesh.name = _objName; //mesh.scale.set(10, 10, 10) mesh.position.set(_position.x, _position.y, _position.z); mesh.rotateX(270/180 * Math.PI) _that.scene.add(mesh); // 将锥形对象添加到场景中 }); } getObjectPosition(targetObject) { let targetCenter = new THREE.Vector3(); // targetObject.updateMatrixWorld(true); // 确保目标物体的世界矩阵是最新的 // targetObject.getWorldPosition(targetCenter); // 将目标物体的世界位置存储到 targetCenter 中 // // 将源物体的位置设置为目标物体的中心位置 // console.log(targetCenter); let box = new THREE.Box3().setFromObject(targetObject); targetCenter.x = (box.max.x + box.min.x) / 2; targetCenter.y = box.min.y; targetCenter.z = (box.max.z + box.min.z) / 2; return { x: targetCenter.x, y: targetCenter.y, z: targetCenter.z }; } getObjectHeight(targetObject) { let box = new THREE.Box3().setFromObject(targetObject); // 获取包围盒的高度 let height = box.max.y - box.min.y; return height; } addShap(_object) { this.shapes.push({ object: _object, y: _object.position.y }); } upDownShap() { if (!this.shapes || this.shapes.length < 1) { return; } // 设置移动的速度和幅度 let speed = 1.5; // 移动速度 let amplitude = 5; // 移动幅度 this.shapes.forEach(shape => { let newYPosition = shape.y + amplitude * Math.sin(speed * Date.now() / 1000); shape.object.position.y = newYPosition; }) } lookAtObject(_object) { // 将相机移动到物体位置并面对物体 let _pos = this.getObjectPosition(_object); this.camera.position.set(_pos.x, _pos.y, _pos.z); this.camera.lookAt(_object.position); } }