Files
aida_front/src/component/HomePage/index/model/patternMaking3D/three.vue
2025-04-16 10:43:54 +08:00

397 lines
13 KiB
Vue
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.
<template>
<div class="three">
<div class="model" ref="threeDom">
</div>
<div class="load" v-show="load.state">
<i class="fi fi-rr-cubes"></i>
<div class="text">Load...</div>
<div class="loadBox">
<div class="schedule" :style="{width:load.progress+'%'}"></div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent,computed,shallowRef,provide,nextTick,onMounted,toRefs, reactive} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Https } from "@/tool/https";
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
// @ts-ignore
import * as THREE from 'three';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import gsap from 'gsap';
import { env } from 'echarts';
export default defineComponent({
components:{
},
props:{
},
emits:[],
setup(props,{emit}) {
const store = useStore();
const data = reactive({
scene:shallowRef() as any,//场景
group:shallowRef() as any,//组
camera:shallowRef() as any,//相机
renderer:shallowRef() as any,//渲染器
pointLight:shallowRef() as any,//光
controls:shallowRef() as any,//监听鼠标、键盘事件
load:{
state:false,
progress:0 as any,
}
})
const dataDom = reactive({
threeDom:null as any,
})
const init = ()=>{
data.scene = new THREE.Scene();
data.group = new THREE.Group()
data.scene.add(data.group)
//创建相机对象
// this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
data.camera = new THREE.PerspectiveCamera(45, dataDom.threeDom.offsetWidth / dataDom.threeDom.offsetHeight, 0.1, 10000);
data.camera.position.set(0, 90, 6); //设置相机位置
data.camera.lookAt(data.scene.position); //设置相机方向(指向的场景对象)
/**
* 创建渲染器对象
*/
let width = dataDom.threeDom.offsetWidth; //窗口宽度
let height = dataDom.threeDom.offsetHeight; //窗口高度
data.renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true,//深度缓存 防止模型闪烁重影
});
// data.renderer.outpuEncoding = THREE?.RGBEEncoding//设置输出颜色编码格式
data.renderer.toneMapping = THREE.ACESFilmicToneMapping;//设置色调
data.renderer.toneMappingExposure = 1.3;
data.renderer.shadowMap.enabled = true;
data.renderer.setPixelRatio(window.devicePixelRatio);
data.renderer.setSize(width, height); //设置渲染区域尺寸
data.renderer.setClearColor(0xffffff, 1); //设置背景颜色
dataDom.threeDom.innerHTML = '';
dataDom.threeDom.appendChild(data.renderer.domElement);
// 设置渲染器大小
//环境光
let ambient = new THREE.AmbientLight(0xffffff,.8);
data.scene.add(ambient);
data.controls = new OrbitControls(data.camera,data.renderer.domElement)//监听鼠标、键盘事件;
// data.controls.minDistance = 500; // 设置相机与焦点的最小距离
// data.controls.maxDistance = 4000; // 设置相机与焦点的最大距离
data.controls.mouseButtons = {
// LEFT:THREE.MOUSE.PAN, // 左键 拖动(默认旋转ROTATE)
LEFT:THREE.MOUSE.ROTATE, // 左键 拖动(默认旋转ROTATE)
MIDDLE:THREE.MOUSE.DOLLY, // 滑轮 缩放
RIGHT:THREE.MOUSE.PAN // 右键 旋转默认拖动PAN
// RIGHT:THREE.MOUSE.ROTAafTE // 右键 旋转默认拖动PAN
}
/**
* 光源设置
*/
//点光源
/**
* AmbientLight 环境光
PointLight 点光源
DirectionalLight 平行光,比如太阳光
SpotLight 聚光源
*/
data.pointLight = new THREE.DirectionalLight(0xffffff,.5);
data.pointLight.intensity = 1.2
data.pointLight.castShadow = true//开启阴影
data.pointLight.shadow.mapSize = new THREE.Vector2(width, height)
data.scene.add(data.pointLight); //点光源添加到场景中
// data.pointLight.position.set(400, 200, 300); //点光源位置
data.pointLight.position.y = 400;
data.pointLight.position.z = 200;
data.pointLight.position.x = 200;
let floorGeometry = new THREE.PlaneGeometry(5000, 3000)//地板大小
let floorMaterial = new THREE.MeshPhongMaterial({ color: "#7e7ab0", })
let floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
floorMesh.rotation.x = -0.5 * Math.PI;
floorMesh.receiveShadow = true;
floorMesh.position.y = -0.001;
// data.scene.add(floorMesh);
const textureLoader = new THREE.TextureLoader();
// const texture = textureLoader.load('/3dModel/sketch-thick.jpg');
data.scene.background = new THREE.Color("#fff");
// data.scene.background = texture;
let openModel = (event:any)=>{
let mouse=new THREE.Vector2();
let raycaster=new THREE.Raycaster();
mouse.x=(event.clientX/window.innerWidth)*2-1;
mouse.y=-(event.clientY/window.innerHeight)*2+1;
raycaster.setFromCamera(mouse, data.camera);
let intersects = raycaster.intersectObjects(data.scene.children);
return intersects
}
dataDom.threeDom.ondblclick = (event:any)=>{
let intersects = openModel(event);
if(!intersects || intersects.length<=0) return
const bbox = new THREE.Box3().setFromObject(intersects[0].object);
const size = new THREE.Vector3();
let target2 = bbox.getCenter(size);//获取选中包围起来后的中心坐标
animateCamera(data.camera.position,intersects[0].point,data.controls.target,target2)
}
let isTweening = false;
function animateCamera(current1:any, target1:any, current2:any, target2:any){
if (isTweening) return
isTweening = true
let options = {
x1: current1.x, // 相机当前位置x
y1: current1.y, // 相机当前位置y
z1: current1.z, // 相机当前位置z
x2: current2.x, // 控制当前的中心点x
y2: current2.y, // 控制当前的中心点y
// z2: current2.z // 控制当前的中心点z
}
gsap.to(options,{
x1: 0, // 新的相机位置x
y1: target2.y, // 新的相机位置y
z1: 2500, // 新的相机位置z
x2: 0, // 新的控制中心点位置x
y2: target2.y, // 新的控制中心点位置x
duration:1,
ease:'linear',
onUpdate:()=>{
data.camera.position.x = options.x1;
data.camera.position.y = options.y1;
data.camera.position.z = options.z1;
data.controls.target.x = options.x2;
data.controls.target.y = options.y2;
// data.controls.target.z = object.z2;
data.controls.update();
},
onComplete:()=>{
isTweening = false
}
// z2: target2.z // 新的控制中心点位置x
})
}
// let setHighlight = (obj:any)=>{
// outlinePass.selectedObjects = obj;
// }
data.controls.enableDamping = true;
let animate = function () {
requestAnimationFrame(animate);
// data.renderer.render(data.scene, data.camera);
// model.rotation.x += 0.01; //旋转物体
var vector = data.camera.position.clone()
data.controls.update();
data.renderer.render(data.scene, data.camera);
// point.position.set(vector.x,vector.y,vector.z);
// group.rotation.y += 0.01;
// composer.render();
};
animate();
}
const setModel = async (url:any)=>{
clearModel()
await addModel(url)
// addMaterial()
}
const addMaterial = (url:any)=>{
//添加图片材质
data.load.state = true
let textureLoader = new THREE.TextureLoader()
textureLoader.load(url, // 图片放在public/textures目录下
(texture:any) => {
// 3. 配置纹理参数
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// texture.repeat.set(1, 1); // 纹理重复次数
texture.anisotropy = 32; // 提高纹理清晰度
data.group?.traverse((child:any) => {
if (child.isMesh) {
// 5. 创建新材质(根据需求选择材质类型)
const textureWidth = texture.image.width;
const textureHeight = texture.image.height;
const box = new THREE.Box3().setFromObject(child);
const modelWidth = box.getSize(new THREE.Vector3()).x;
const modelHeight = box.getSize(new THREE.Vector3()).y;
const repeatX = modelWidth / textureWidth;
const repeatY = modelHeight / textureHeight;
// texture.repeat.set(1, 1); // 纹理重复次数
texture.repeat.set(repeatX, repeatY); // 纹理重复次数
const newMaterial = new THREE.MeshStandardMaterial({
map: texture, // 基础颜色贴图
roughness: 0.7, // 表面粗糙度 (0-1)
metalness: .2, // 金属质感 (0-1)
side: THREE.DoubleSide // 双面渲染
});
// 6. 替换原有材质
child.material = newMaterial;
// 7. 如果需要单独控制某些子模型的UV
if (child.geometry.attributes.uv) {
// 可以在这里修改UV坐标
const uvs = child.geometry.attributes.uv.array;
// ...UV操作逻辑...
}
}
data.load.state = false
},(xhr:any) => { // 加载进度回调
const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2);
data.load.progress = percent
// updateProgressBar(Number(percent));
},(error:any) => {
console.error('纹理加载失败:', error);
data.load.state = false
});
})
}
const addModel = async (url:any)=>{
await new Promise((resolve, reject) => {
var fbxLoader = new GLTFLoader();
let drac = new DRACOLoader()
drac.setDecoderPath('/draco/')
fbxLoader.setDRACOLoader(drac)
// fbxLoader.load('/3dModel/222/1111.glb',
fbxLoader.load(url,
(obj:any) => {
let scene = obj.scene;
var box = new THREE.Box3().setFromObject(scene);
var center = box.getCenter(new THREE.Vector3());
data.controls.target.copy(center);
// data.controls.autoRotate = true
data.camera.position.y = center.y;
data.camera.position.z = 1000;
data.pointLight.position.y = 250;
data.pointLight.position.z = 1250;
data.group.add(scene);
resolve('')
},(xhr:any) => { // 加载进度回调
const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2);
data.load.progress = percent
// updateProgressBar(Number(percent));
},(error:any) => { // 加载失败回调
console.error('模型加载失败:', error);
reject('')
})
})
}
const clearModel = ()=>{
const oldGroup:any = data.group;
data.group = new THREE.Group();
data.scene.add(data.group);
data.scene.remove(oldGroup);
}
// const loadThree = ()=>{
// init()
// }
const getModelUrl = (value:any)=>{
return new Promise((resolve, reject) => {
// Https.axiosGet(Https.httpUrls.getThreeDGlb,{params:{threeDSimpleId:value.id},env:{binary:true}}).then((rv)=>{
// //二进制流转本地路径
// console.log(rv)
// resolve(rv)
// }).catch(()=>{
// reject('')
// })
// //fetch get请求携带token
// fetch("https://develop.api.aida.com.hk/api/project/getThreeDGlb?threeDSimpleId=1", {
// headers:{
// authorization:'Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI4OCIsInN1YiI6IntcImNvdW50cnlcIjpcIkNoaW5hXCIsXCJpZFwiOjg4LFwibGFuZ3VhZ2VcIjpcIkVOR0xJU0hcIixcInVzZXJuYW1lXCI6XCJzaGJcIn0iLCJpYXQiOjE3NDMzNDkwNjQsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE3NTE5ODkwNjR9.gmL0JufYy9wd23qCY-ibwhgpXZ2X68WAiHSeC99I4x7cipWyxLaQmuIBk2SJSdWBm0tTN2Mx-etXO9a7MtQmpw',
// }
// }).then(res => {
// return res.blob();
// }).then((res) => {
// var url = URL.createObjectURL(res);
// console.log(url, res)
// resolve(url)
// }).catch(err => {
// console.log(err);
// })
resolve(value.threeDSimpleUrl)
})
}
const openSetData = async (value:any)=>{
if(!data.scene){
init()
}
data.load.state = true
const modeUrl = await getModelUrl(value)
await setModel(modeUrl)
data.load.state = false
}
onMounted(()=>{
})
return{
...toRefs(dataDom),
...toRefs(data),
openSetData,
addMaterial,
}
},
provide() {
return {
}
},
})
</script>
<style lang="less" scoped>
.three{
width: 100%;
height: 100%;
position: relative;
flex: 1;
overflow: hidden;
> .model{
width: 100%;
height: 100%;
}
> .load{
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, .2);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: #fff;
> i{
font-size: 3rem;
}
> .loadBox{
width: 15rem;
height: 1rem;
border-radius: 1rem;
background: #fff;
overflow: hidden;
> .schedule{
height: 100%;
background: greenyellow;
}
}
}
}
</style>