Files
PropertyDeployment/resources/Web/MicroCommunityIotWeb/html/api/3d/Cockpit3d.js
2025-12-09 20:22:03 +08:00

362 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}