|
|
@@ -1,6 +1,24 @@
|
|
|
|
|
|
<template>
|
|
|
- <div ref="threeCanvas" style="width: 100%; height: 100%"></div>
|
|
|
+ <div class="modelBox">
|
|
|
+ <div ref="threeCanvas" style="width: 100%; height: 100%"></div>
|
|
|
+ <div class="bjSelectBox">
|
|
|
+ <el-select
|
|
|
+ v-model="bj"
|
|
|
+ placeholder="完整展示"
|
|
|
+ style="width: 300px"
|
|
|
+ clearable
|
|
|
+ @change="bjChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in bjList"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.uuid"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
@@ -62,6 +80,9 @@ export default {
|
|
|
orbitControls: null,
|
|
|
css3DRenderer: null,
|
|
|
animationId: null,
|
|
|
+ basicModel: null,
|
|
|
+ bj: "",
|
|
|
+ bjList: [],
|
|
|
};
|
|
|
},
|
|
|
|
|
|
@@ -74,6 +95,27 @@ export default {
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
+ bjChange(uuid) {
|
|
|
+ let modelBjList = this.scene.children.find((modelEle) => {
|
|
|
+ return !modelEle.isLight && !modelEle.isLineSegments;
|
|
|
+ });
|
|
|
+ if (uuid) {
|
|
|
+ modelBjList.children.forEach((ele) => {
|
|
|
+ if (ele.uuid === uuid) {
|
|
|
+ this.focusOnPart(ele);
|
|
|
+ ele.visible = true;
|
|
|
+ } else {
|
|
|
+ ele.visible = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.focusOnPart(this.basicModel);
|
|
|
+ modelBjList.children.forEach((ele) => {
|
|
|
+ ele.visible = true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
initThree(options) {
|
|
|
this.$nextTick(() => {
|
|
|
const parentDom = getComputedStyle(
|
|
|
@@ -111,24 +153,24 @@ export default {
|
|
|
});
|
|
|
|
|
|
// 添加灯光
|
|
|
- const ambientLight = new THREE.AmbientLight(0xffffff, 1);
|
|
|
+ const ambientLight = new THREE.AmbientLight(0xffffff, 5);
|
|
|
scene.add(ambientLight);
|
|
|
- const directionalLight1 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight1 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight1.position.set(0, 0, 1);
|
|
|
scene.add(directionalLight1);
|
|
|
- const directionalLight2 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight2 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight2.position.set(0, 0, -1);
|
|
|
scene.add(directionalLight2);
|
|
|
- const directionalLight3 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight3 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight3.position.set(1, 0, 0);
|
|
|
scene.add(directionalLight3);
|
|
|
- const directionalLight4 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight4 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight4.position.set(-1, 0, 0);
|
|
|
scene.add(directionalLight4);
|
|
|
- const directionalLight5 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight5 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight5.position.set(0, 1, 0);
|
|
|
scene.add(directionalLight5);
|
|
|
- const directionalLight6 = new THREE.DirectionalLight(0xffffff, 5);
|
|
|
+ const directionalLight6 = new THREE.DirectionalLight(0xffffff, 3);
|
|
|
directionalLight6.position.set(0, -1, 0);
|
|
|
scene.add(directionalLight6);
|
|
|
|
|
|
@@ -205,7 +247,7 @@ export default {
|
|
|
options?.dom?.appendChild(warning);
|
|
|
}
|
|
|
|
|
|
- this.addModel({ fileName: "SolarPowerPlant" });
|
|
|
+ this.addModel({ fileName: "fengji" });
|
|
|
});
|
|
|
},
|
|
|
|
|
|
@@ -230,42 +272,134 @@ export default {
|
|
|
const loader = this.loader[fileType];
|
|
|
loader.setResourcePath(`./static/model/${fileName}/`);
|
|
|
loader.load(`./static/model/${fileName}/model.${fileType}`, (result) => {
|
|
|
- const model = result.scene;
|
|
|
+ this.basicModel = this.formatModel(result.scene);
|
|
|
+ this.basicModel.isCurrentAddModel = true;
|
|
|
|
|
|
- const camera = this.camera;
|
|
|
+ let bjList = [];
|
|
|
+ this.basicModel.children.forEach((bj) => {
|
|
|
+ bjList.push(bj);
|
|
|
+ });
|
|
|
|
|
|
- // model.traverse((child) => {
|
|
|
- // if (child.material) {
|
|
|
- // child.material.emissive = child.material.color;
|
|
|
- // child.material.emissiveMap = child.material.map;
|
|
|
- // }
|
|
|
- // });
|
|
|
+ this.bjList = bjList;
|
|
|
|
|
|
- // model.children.forEach((item, index) => {
|
|
|
- // item.castShadow = true;
|
|
|
- // item.receiveShadow = true;
|
|
|
- // });
|
|
|
+ this.collectMaterialInfo(this.basicModel);
|
|
|
+ this.scene.add(this.basicModel);
|
|
|
+ });
|
|
|
+ },
|
|
|
|
|
|
- const baseBoxSize = new THREE.Vector3(100, 100, 100);
|
|
|
- // 计算模型的包围盒(bounding box)
|
|
|
- const boundingBox = new THREE.Box3().setFromObject(model);
|
|
|
- const modelSize = new THREE.Vector3();
|
|
|
- boundingBox.getSize(modelSize);
|
|
|
+ // 调整视角到选中部件
|
|
|
+ focusOnPart(part) {
|
|
|
+ const box = new THREE.Box3().setFromObject(part);
|
|
|
+ const size = box.getSize(new THREE.Vector3());
|
|
|
+ const center = box.getCenter(new THREE.Vector3());
|
|
|
+ const speed = 0.5;
|
|
|
+
|
|
|
+ // 计算最佳相机距离
|
|
|
+ const maxDim = Math.max(size.x, size.y, size.z);
|
|
|
+ const cameraDistance = maxDim * parseFloat(speed);
|
|
|
+
|
|
|
+ // 计算相机位置
|
|
|
+ const direction = this.orbitControls.target
|
|
|
+ .clone()
|
|
|
+ .sub(this.camera.position)
|
|
|
+ .normalize();
|
|
|
+ const cameraPosition = center
|
|
|
+ .clone()
|
|
|
+ .sub(direction.multiplyScalar(cameraDistance));
|
|
|
+
|
|
|
+ // 是否启用平滑过渡
|
|
|
+ const smooth = true;
|
|
|
+
|
|
|
+ if (smooth) {
|
|
|
+ // 平滑过渡到新位置
|
|
|
+ this.animateCamera(
|
|
|
+ this.camera.position,
|
|
|
+ cameraPosition,
|
|
|
+ this.orbitControls.target,
|
|
|
+ center,
|
|
|
+ speed
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ // 直接设置新位置
|
|
|
+ this.camera.position.copy(cameraPosition);
|
|
|
+ this.orbitControls.target.copy(center);
|
|
|
+ this.orbitControls.update();
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- // 计算模型的最大尺寸
|
|
|
- const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
|
|
|
+ // 平滑移动相机
|
|
|
+ animateCamera(fromPosition, toPosition, fromTarget, toTarget, speed) {
|
|
|
+ const that = this;
|
|
|
+ const duration = 1000; // 动画持续时间(ms)
|
|
|
+ const startTime = Date.now();
|
|
|
|
|
|
- // 计算缩放比例,使模型尺寸不超过基准盒子的尺寸
|
|
|
- const scaleFactor = baseBoxSize.clone().divideScalar(maxModelSize);
|
|
|
+ // 缓动函数
|
|
|
+ let easeInOutCubic = (t) => {
|
|
|
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
|
+ };
|
|
|
|
|
|
- // 将模型等比例缩放到适应基准盒子大小
|
|
|
- model.scale.set(scaleFactor.x, scaleFactor.y, scaleFactor.z);
|
|
|
+ function update() {
|
|
|
+ const elapsed = Date.now() - startTime;
|
|
|
+ const progress = Math.min(elapsed / duration, 1);
|
|
|
|
|
|
- camera.position.z = 200;
|
|
|
- console.log(222, model);
|
|
|
- this.collectMaterialInfo(model);
|
|
|
- this.scene.add(model);
|
|
|
- });
|
|
|
+ // 使用缓动函数
|
|
|
+ const easeProgress = easeInOutCubic(progress);
|
|
|
+
|
|
|
+ // 插值计算相机位置和目标位置
|
|
|
+ that.camera.position.lerpVectors(
|
|
|
+ fromPosition,
|
|
|
+ toPosition,
|
|
|
+ easeProgress
|
|
|
+ );
|
|
|
+ that.orbitControls.target.lerpVectors(
|
|
|
+ fromTarget,
|
|
|
+ toTarget,
|
|
|
+ easeProgress
|
|
|
+ );
|
|
|
+ that.orbitControls.update();
|
|
|
+
|
|
|
+ if (progress < 1) {
|
|
|
+ requestAnimationFrame(update);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ update();
|
|
|
+ },
|
|
|
+
|
|
|
+ formatModel(model) {
|
|
|
+ this.renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
|
+ const camera = this.camera;
|
|
|
+
|
|
|
+ // model.traverse((child) => {
|
|
|
+ // if (child.material) {
|
|
|
+ // child.material.emissive = child.material.color;
|
|
|
+ // child.material.emissiveMap = child.material.map;
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+
|
|
|
+ // model.children.forEach((item, index) => {
|
|
|
+ // item.castShadow = true;
|
|
|
+ // item.receiveShadow = true;
|
|
|
+ // });
|
|
|
+
|
|
|
+ const baseBoxSize = new THREE.Vector3(100, 100, 100);
|
|
|
+ // 计算模型的包围盒(bounding box)
|
|
|
+ const boundingBox = new THREE.Box3().setFromObject(model);
|
|
|
+ const modelSize = new THREE.Vector3();
|
|
|
+ boundingBox.getSize(modelSize);
|
|
|
+
|
|
|
+ // 计算模型的最大尺寸
|
|
|
+ const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
|
|
|
+
|
|
|
+ // 计算缩放比例,使模型尺寸不超过基准盒子的尺寸
|
|
|
+ const scaleFactor = baseBoxSize.clone().divideScalar(maxModelSize);
|
|
|
+
|
|
|
+ // 将模型等比例缩放到适应基准盒子大小
|
|
|
+ model.scale.set(scaleFactor.x, scaleFactor.y, scaleFactor.z);
|
|
|
+
|
|
|
+ camera.position.z = 200;
|
|
|
+ console.log("model", model);
|
|
|
+ return model;
|
|
|
},
|
|
|
|
|
|
collectMaterialInfo(model) {
|
|
|
@@ -285,29 +419,26 @@ export default {
|
|
|
|
|
|
materials.forEach((material, index) => {
|
|
|
if (material.name === "Mesh_225") {
|
|
|
-
|
|
|
}
|
|
|
info.push(`材质 ${materialCount}-${index + 1}: ${material.type}`);
|
|
|
- info.push(` 名称: ${material.name || "未命名"}`);
|
|
|
- info.push(
|
|
|
- ` 颜色: ${
|
|
|
- material.color ? material.color.getHexString() : "无"
|
|
|
- }`
|
|
|
- );
|
|
|
- info.push(` 贴图: ${material.map ? "有" : "无"}`);
|
|
|
- info.push(` 自发光贴图: ${material.emissiveMap ? "有" : "无"}`);
|
|
|
- info.push(
|
|
|
- ` 金属粗糙度贴图: ${material.metalnessMap ? "有" : "无"}`
|
|
|
- );
|
|
|
- info.push(` 法线贴图: ${material.normalMap ? "有" : "无"}`);
|
|
|
- info.push("---");
|
|
|
-
|
|
|
- // 保存原始材质引用
|
|
|
- originalMaterials.set(node.uuid + "-" + index, material);
|
|
|
+ info.push(` 名称: ${material.name || "未命名"}`);
|
|
|
+ info.push(
|
|
|
+ ` 颜色: ${material.color ? material.color.getHexString() : "无"}`
|
|
|
+ );
|
|
|
+ info.push(` 贴图: ${material.map ? "有" : "无"}`);
|
|
|
+ info.push(` 自发光贴图: ${material.emissiveMap ? "有" : "无"}`);
|
|
|
+ info.push(
|
|
|
+ ` 金属粗糙度贴图: ${material.metalnessMap ? "有" : "无"}`
|
|
|
+ );
|
|
|
+ info.push(` 法线贴图: ${material.normalMap ? "有" : "无"}`);
|
|
|
+ info.push("---");
|
|
|
+
|
|
|
+ // 保存原始材质引用
|
|
|
+ originalMaterials.set(node.uuid + "-" + index, material);
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
- console.log(333, info);
|
|
|
+ // console.log(333, info);
|
|
|
},
|
|
|
|
|
|
overrideMaterials() {
|
|
|
@@ -386,4 +517,18 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
};
|
|
|
-</script>
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.modelBox {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .bjSelectBox {
|
|
|
+ position: absolute;
|
|
|
+ left: 5px;
|
|
|
+ top: 5px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|