初始提交:全国牛产业大数据中心大屏项目
This commit is contained in:
302
src/utils/Earth.js
Normal file
302
src/utils/Earth.js
Normal file
@@ -0,0 +1,302 @@
|
||||
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: 0x000000,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user