Files
datav---Cattle-Industry/src/utils/Earth.js
2025-11-26 17:31:42 +08:00

303 lines
9.4 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/examples/jsm/controls/OrbitControls';
import Stats from 'three/examples/jsm/libs/stats.module';
import TWEEN from '@tweenjs/tween.js';
import { deepMerge, isType } from '@/utils';
export default class Earth3d {
constructor(options = {}) {
let defaultOptions = {
isFull: true,
container: null,
width: window.innerWidth,
height: window.innerHeight,
bgColor: 0x0e2438, // 深蓝偏亮背景,替代纯黑以提升亮度
materialColor: 0xff0000,
controls: {
visibel: true, // 是否开启
enableDamping: true, // 阻尼
autoRotate: false, // 自动旋转
maxPolarAngle: Math.PI, // 相机垂直旋转角度的上限
},
statsVisibel: true,
axesVisibel: true,
axesHelperSize: 250, // 左边尺寸
};
this.options = deepMerge(defaultOptions, options);
this.container = document.querySelector(this.options.container);
// 确保容器尺寸有效避免Canvas尺寸为0的错误
this.options.width = Math.max(this.container.offsetWidth, 800);
this.options.height = Math.max(this.container.offsetHeight, 600);
// 如果容器尺寸仍然为0使用默认值
if (this.options.width === 0 || this.options.height === 0) {
this.options.width = 800;
this.options.height = 600;
}
this.scene = new THREE.Scene(); // 场景
this.camera = null; // 相机
this.renderer = null; // 渲染器
this.mesh = null; // 网格
this.animationStop = null; // 用于停止动画
this.controls = null; // 轨道控制器
this.stats = null; // 统计
this.init();
}
init() {
this.initStats();
this.initCamera();
this.initModel();
this.initRenderer(); // 异步初始化其他组件在continueInit中初始化
}
async initModel() {}
/**
* 运行
*/
run() {
// 如果渲染器已经准备好,直接开始循环
if (this.renderer) {
this.loop();
} else {
// 否则标记需要启动,等待渲染器创建完成
this.shouldStart = true;
}
}
// 循环
loop() {
// 检查渲染器是否存在
if (!this.renderer) {
return;
}
this.animationStop = window.requestAnimationFrame(() => {
this.loop();
});
// 这里是你自己业务上需要的code
this.renderer.render(this.scene, this.camera);
// 控制相机旋转缩放的更新
if (this.options.controls.visibel) this.controls.update();
// 统计更新
if (this.options.statsVisibel) this.stats.update();
TWEEN.update();
}
initCamera() {
let { width, height } = this.options;
let rate = width / height;
// 设置45°的透视相机,更符合人眼观察
this.camera = new THREE.PerspectiveCamera(45, rate, 0.1, 1500);
// this.camera.position.set(-428.88, 861.97, -1438.0)
this.camera.position.set(270.27, 173.24, 257.54);
// this.camera.position.set(-102, 205, -342)
this.camera.lookAt(0, 0, 0);
}
/**
* 初始化渲染器
*/
initRenderer() {
let { width, height, bgColor } = this.options;
// 强制清理所有WebGL上下文
if (this.renderer) {
this.renderer.dispose();
this.renderer.forceContextLoss();
this.renderer = null;
}
// 清理容器中现有的所有子元素
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
// 强制垃圾回收
if (window.gc) {
window.gc();
}
// 延迟创建新的渲染器,确保上下文完全释放
setTimeout(() => {
try {
// 重新获取容器尺寸,确保有效
const containerWidth = this.container.offsetWidth || window.innerWidth;
const containerHeight = this.container.offsetHeight || window.innerHeight;
// 确保尺寸有效
const validWidth = Math.max(containerWidth, 800);
const validHeight = Math.max(containerHeight, 600);
// 更新options中的尺寸
this.options.width = validWidth;
this.options.height = validHeight;
// 创建一个新的canvas元素
const canvas = document.createElement('canvas');
let renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
preserveDrawingBuffer: false,
powerPreference: "high-performance"
});
// 设置canvas的分辨率
renderer.setPixelRatio(window.devicePixelRatio);
// 设置canvas 的尺寸大小
renderer.setSize(validWidth, validHeight);
// 设置背景色
renderer.setClearColor(bgColor, 1);
// 设置canvas的z-index确保CSS2D渲染器在其之上
renderer.domElement.style.zIndex = '1';
renderer.domElement.style.position = 'absolute';
// 插入到dom中
this.container.appendChild(renderer.domElement);
this.renderer = renderer;
// 继续初始化其他组件
this.continueInit();
} catch (error) {
console.error('WebGL渲染器创建失败:', error);
// 创建一个错误提示
const errorDiv = document.createElement('div');
errorDiv.innerHTML = '3D渲染器初始化失败请刷新页面重试';
errorDiv.style.cssText = 'color: #ff6b6b; text-align: center; padding: 50px; font-size: 16px;';
this.container.appendChild(errorDiv);
}
}, 100);
}
continueInit() {
// 原来在init方法中renderer初始化后的逻辑
this.initLight();
this.initStats();
this.initControls();
this.initAxes();
// 如果之前调用了run方法现在启动循环
if (this.shouldStart) {
this.shouldStart = false;
this.loop();
}
}
initLight() {
// 平行光1
let directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight1.position.set(400, 200, 200);
// 平行光2
let directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight2.position.set(-400, -200, -300);
// 环境光
let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
// 将光源添加到场景中
this.addObject(directionalLight1);
this.addObject(directionalLight2);
this.addObject(ambientLight);
}
initStats() {
if (!this.options.statsVisibel) return false;
// 确保容器有有效的尺寸
if (!this.container || this.container.offsetWidth === 0 || this.container.offsetHeight === 0) {
console.warn('Container not ready for stats initialization, skipping...');
return false;
}
this.stats = new Stats();
// 确保stats的DOM元素有正确的尺寸
if (this.stats.dom) {
this.stats.dom.style.position = 'absolute';
this.stats.dom.style.top = '0px';
this.stats.dom.style.left = '0px';
this.stats.dom.style.zIndex = '100';
}
this.container.appendChild(this.stats.dom);
}
initControls() {
try {
let {
controls: { enableDamping, autoRotate, visibel, maxPolarAngle },
} = this.options;
if (!visibel) return false;
// 轨道控制器,使相机围绕目标进行轨道运动(旋转|缩放|平移)
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.maxPolarAngle = maxPolarAngle;
this.controls.autoRotate = autoRotate;
this.controls.enableDamping = enableDamping;
} catch (error) {
console.log(error);
}
}
initAxes() {
if (!this.options.axesVisibel) return false;
var axes = new THREE.AxesHelper(this.options.axesHelperSize);
this.addObject(axes);
}
// 清空dom
empty(elem) {
while (elem && elem.lastChild) elem.removeChild(elem.lastChild);
}
/**
* 添加对象到场景
* @param {*} object {} []
*/
addObject(object) {
if (isType('Array', object)) {
this.scene.add(...object);
} else {
this.scene.add(object);
}
}
/**
* 移除对象
* @param {*} object {} []
*/
removeObject(object) {
if (isType('Array', object)) {
object.map((item) => {
item.geometry.dispose();
});
this.scene.remove(...object);
} else {
object.geometry.dispose();
this.scene.remove(object);
}
}
/**
* 重置
*/
resize() {
// 重新设置宽高
let newWidth = this.container.offsetWidth || window.innerWidth;
let newHeight = this.container.offsetHeight || window.innerHeight;
// 确保尺寸有效
this.options.width = Math.max(newWidth, 800);
this.options.height = Math.max(newHeight, 600);
if (this.renderer) {
this.renderer.setSize(this.options.width, this.options.height);
}
// 重新设置相机的位置
let rate = this.options.width / this.options.height;
// 必須設置相機的比例,重置的時候才不会变形
this.camera.aspect = rate;
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
// 如果相机的一些属性发生了变化需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
this.camera.updateProjectionMatrix();
// 如果stats还没有初始化可能之前容器尺寸为0现在重新尝试初始化
if (this.options.statsVisibel && !this.stats) {
this.initStats();
}
}
}