|
|
@@ -25,7 +25,6 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
|
|
|
|
import { get3DModelData } from '@/views/book/courseware/data/3dModel';
|
|
|
import { toggleFullScreen } from '@/utils/common';
|
|
|
-import { GetFileStoreInfo } from '@/api/app';
|
|
|
|
|
|
export default {
|
|
|
name: 'ThreeModelPreview',
|
|
|
@@ -33,11 +32,12 @@ export default {
|
|
|
data() {
|
|
|
return {
|
|
|
data: get3DModelData(),
|
|
|
- scene: null,
|
|
|
- camera: null,
|
|
|
- renderer: null,
|
|
|
- controls: null,
|
|
|
- animationId: null,
|
|
|
+ scene: null, // 三维场景
|
|
|
+ camera: null, // 相机
|
|
|
+ mixer: null, // 动画混合器
|
|
|
+ renderer: null, // 渲染器
|
|
|
+ controls: null, // 轨道控制器
|
|
|
+ animationId: null, // 动画帧ID
|
|
|
isFullScreen: false, // 是否全屏
|
|
|
loaded: false, // 是否加载完成
|
|
|
};
|
|
|
@@ -81,40 +81,40 @@ export default {
|
|
|
// 创建相机
|
|
|
const width = container.clientWidth;
|
|
|
const height = container.clientHeight;
|
|
|
- this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
|
|
- this.camera.position.set(5, 5, 5);
|
|
|
+ this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); // 视角75度
|
|
|
+ this.camera.position.set(5, 5, 5); // 设置相机位置
|
|
|
|
|
|
// 创建渲染器
|
|
|
- this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
|
+ this.renderer = new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿
|
|
|
this.renderer.setSize(width, height);
|
|
|
- this.renderer.shadowMap.enabled = true;
|
|
|
- this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
|
+ this.renderer.shadowMap.enabled = true; // 启用阴影映射
|
|
|
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 使用柔和阴影
|
|
|
|
|
|
// 启用颜色管理和正确的色彩空间
|
|
|
- this.renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
|
- this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
|
- this.renderer.toneMappingExposure = 1.0;
|
|
|
+ this.renderer.outputColorSpace = THREE.SRGBColorSpace; // 设置输出色彩空间
|
|
|
+ this.renderer.toneMapping = THREE.ACESFilmicToneMapping; // 使用ACES电影色调映射
|
|
|
+ this.renderer.toneMappingExposure = 1.0; // 曝光度
|
|
|
|
|
|
- container.appendChild(this.renderer.domElement);
|
|
|
+ container.appendChild(this.renderer.domElement); // 将渲染器添加到DOM
|
|
|
|
|
|
// 添加光源
|
|
|
- const ambientLight = new THREE.AmbientLight(0x404040, 1.0); // 增加环境光强度
|
|
|
- this.scene.add(ambientLight);
|
|
|
+ const ambientLight = new THREE.AmbientLight(0xffffff); // 环境光
|
|
|
+ this.scene.add(ambientLight); // 添加环境光到场景
|
|
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0); // 增加方向光强度
|
|
|
- directionalLight.position.set(10, 10, 5);
|
|
|
- directionalLight.castShadow = true;
|
|
|
- this.scene.add(directionalLight);
|
|
|
+ directionalLight.position.set(10, 10, 5); // 设置光源位置
|
|
|
+ directionalLight.castShadow = true; // 启用阴影投射
|
|
|
+ this.scene.add(directionalLight); // 添加方向光到场景
|
|
|
|
|
|
// 添加额外的填充光
|
|
|
- const fillLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
|
- fillLight.position.set(-10, -10, -5);
|
|
|
- this.scene.add(fillLight);
|
|
|
+ const fillLight = new THREE.DirectionalLight(0xffffff, 0.5); // 较低强度的填充光
|
|
|
+ fillLight.position.set(-10, -10, -5); // 设置填充光位置
|
|
|
+ this.scene.add(fillLight); // 添加填充光到场景
|
|
|
|
|
|
// 添加控制器
|
|
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
|
- this.controls.enableDamping = true;
|
|
|
- this.controls.dampingFactor = 0.25;
|
|
|
+ this.controls.enableDamping = true; // 启用阻尼效果
|
|
|
+ this.controls.dampingFactor = 0.25; // 阻尼系数
|
|
|
|
|
|
// 开始渲染循环
|
|
|
this.animate();
|
|
|
@@ -128,12 +128,11 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const fileStore = await GetFileStoreInfo({ file_id: this.data.model_list[0].file_id });
|
|
|
- if (!fileStore || fileStore.error) {
|
|
|
- console.error('模型文件加载失败:', fileStore);
|
|
|
+ const modelUrl = this.data.file_list[0].file_url;
|
|
|
+ if (!modelUrl) {
|
|
|
+ console.error('模型文件URL不存在');
|
|
|
return;
|
|
|
}
|
|
|
- const modelUrl = fileStore.file_url;
|
|
|
// 根据文件扩展名选择合适的加载器
|
|
|
const extension = modelUrl.split('.').pop().toLowerCase();
|
|
|
let loader = null;
|
|
|
@@ -148,9 +147,9 @@ export default {
|
|
|
loader = new FBXLoader();
|
|
|
this.loadFBX(loader, modelUrl);
|
|
|
break;
|
|
|
- case 'obj':
|
|
|
+ case 'zip':
|
|
|
loader = new OBJLoader();
|
|
|
- this.loadOBJ(loader, modelUrl);
|
|
|
+ this.loadOBJ(loader, this.data.obj_info.obj_file_url);
|
|
|
break;
|
|
|
default:
|
|
|
console.error('不支持的模型格式:', extension);
|
|
|
@@ -240,16 +239,13 @@ export default {
|
|
|
|
|
|
async loadOBJ(loader, url) {
|
|
|
// 首先尝试加载MTL材质文件
|
|
|
- let mtlUrl = '';
|
|
|
- const mtlLoader = new MTLLoader();
|
|
|
- if (this.data.model_list.length > 1) {
|
|
|
- const fileStore = await GetFileStoreInfo({ file_id: this.data.model_list[1].file_id });
|
|
|
- if (!fileStore || fileStore.error) {
|
|
|
- console.error('mtl 材质文件加载失败:', fileStore);
|
|
|
- } else {
|
|
|
- mtlUrl = fileStore.file_url;
|
|
|
- }
|
|
|
+ let mtlUrl = this.data.mtl_info.mtl_file_url;
|
|
|
+ if (!mtlUrl) {
|
|
|
+ // 如果没有提供MTL文件URL,直接加载OBJ模型
|
|
|
+ this.loadOBJModel(loader, url);
|
|
|
+ return;
|
|
|
}
|
|
|
+ const mtlLoader = new MTLLoader();
|
|
|
|
|
|
// 设置材质加载路径
|
|
|
const basePath = mtlUrl.substring(0, mtlUrl.lastIndexOf('/') + 1);
|
|
|
@@ -262,10 +258,9 @@ export default {
|
|
|
mtlLoader.load(
|
|
|
mtlFileName,
|
|
|
(materials) => {
|
|
|
- // 材质文件加载成功
|
|
|
- materials.preload();
|
|
|
- loader.setMaterials(materials);
|
|
|
- this.loadOBJModel(loader, url, true);
|
|
|
+ materials.preload(); // 预加载
|
|
|
+ loader.setMaterials(materials); // 将材质应用到OBJ加载器
|
|
|
+ this.loadOBJModel(loader, url);
|
|
|
},
|
|
|
undefined,
|
|
|
(error) => {
|
|
|
@@ -280,26 +275,11 @@ export default {
|
|
|
* 加载OBJ模型并应用默认材质
|
|
|
* @param {OBJLoader} loader - OBJ加载器
|
|
|
* @param {string} url - OBJ文件URL
|
|
|
- * @param {boolean} hasMaterials - 是否有材质文件
|
|
|
*/
|
|
|
- loadOBJModel(loader, url, hasMaterials = false) {
|
|
|
+ loadOBJModel(loader, url) {
|
|
|
loader.load(
|
|
|
url,
|
|
|
(obj) => {
|
|
|
- let colorList = [0xff6666, 0x66ff66, 0x6666ff, 0xffff66, 0x66ffff, 0xff66ff]; // 可自定义颜色列表
|
|
|
- let meshIndex = 0;
|
|
|
- obj.traverse((child) => {
|
|
|
- if (child.isMesh && !hasMaterials) {
|
|
|
- // 为每个 mesh 设置自定义颜色材质
|
|
|
- child.material = new THREE.MeshPhongMaterial({
|
|
|
- color: colorList[meshIndex % colorList.length], // 循环使用颜色
|
|
|
- shininess: 30,
|
|
|
- });
|
|
|
- meshIndex += 1;
|
|
|
- child.castShadow = true;
|
|
|
- child.receiveShadow = true;
|
|
|
- }
|
|
|
- });
|
|
|
this.addModelToScene(obj);
|
|
|
},
|
|
|
(progress) => {
|
|
|
@@ -313,6 +293,10 @@ export default {
|
|
|
);
|
|
|
},
|
|
|
|
|
|
+ /**
|
|
|
+ * 将模型添加到场景中并调整相机位置
|
|
|
+ * @param {THREE.Object3D} model - 3D模型对象
|
|
|
+ */
|
|
|
addModelToScene(model) {
|
|
|
// 计算模型的包围盒,用于自动调整相机位置
|
|
|
const box = new THREE.Box3().setFromObject(model);
|
|
|
@@ -353,6 +337,9 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ /**
|
|
|
+ * 窗口大小变化处理函数
|
|
|
+ */
|
|
|
onWindowResize() {
|
|
|
const container = this.$refs.three;
|
|
|
this.isFullScreen = document.fullscreenElement !== null;
|
|
|
@@ -403,6 +390,9 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ /**
|
|
|
+ * 切换全屏模式
|
|
|
+ */
|
|
|
handleToggleFullScreen() {
|
|
|
if (!this.$refs.three) return;
|
|
|
if (!this.loaded) {
|
|
|
@@ -410,7 +400,6 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
toggleFullScreen(this.$refs.three);
|
|
|
- // 延迟一帧来确保DOM已经更新
|
|
|
this.$nextTick(() => {
|
|
|
setTimeout(() => {
|
|
|
this.onWindowResize();
|