Merge branch 'main' of http://18.167.251.121:10003/aidlab/FiDA_Front
This commit is contained in:
BIN
src/assets/images/three/sample.glb
Normal file
BIN
src/assets/images/three/sample.glb
Normal file
Binary file not shown.
@@ -4,7 +4,8 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import gsap from 'gsap';
|
||||
import * as THREE from 'three';
|
||||
import { initThree,clearModel,addModel } from './threeTool'
|
||||
import { initThree,clearModel,addModel,getModelInfo,calculateCameraPosition } from './threeTool'
|
||||
import threeGlb from '@/assets/images/three/sample.glb'
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
@@ -28,27 +29,56 @@ const load = ref({
|
||||
progress:0
|
||||
})
|
||||
// const textureLoader = ref(new THREE.TextureLoader())//材质
|
||||
//初始化
|
||||
const init = ()=>{
|
||||
//初始化threejs
|
||||
if(scene.value)return
|
||||
const initResult = initThree(threeDom.value)
|
||||
scene.value = initResult.scene
|
||||
group.value = initResult.group
|
||||
camera.value = initResult.camera
|
||||
renderer.value = initResult.renderer
|
||||
ambient.value = initResult.ambient
|
||||
controls.value = initResult.controls
|
||||
pointLight.value = initResult.pointLight
|
||||
const init = () => {
|
||||
//初始化threejs
|
||||
if (scene.value) return
|
||||
const initResult = initThree(threeDom.value)
|
||||
scene.value = initResult.scene
|
||||
group.value = initResult.group
|
||||
camera.value = initResult.camera
|
||||
renderer.value = initResult.renderer
|
||||
ambient.value = initResult.ambient
|
||||
controls.value = initResult.controls
|
||||
pointLight.value = initResult.pointLight
|
||||
|
||||
threeDom.value.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(camera.value.position,intersects[0].point,controls.value.target,target2)
|
||||
}
|
||||
threeDom.value.ondblclick = (event:any)=>{
|
||||
let intersects = openModel(event);
|
||||
if(!intersects || intersects.length<=0) return
|
||||
|
||||
const clickedObject = intersects[0].object;
|
||||
const modelInfo = getModelInfo(clickedObject);
|
||||
const { size } = modelInfo;
|
||||
const maxSize = Math.max(size.x, size.y, size.z);
|
||||
let distanceFactor = 1.2;
|
||||
let heightFactor = 0.3;
|
||||
let angle = 0;
|
||||
if (size.y > size.x * 2) {
|
||||
// 高瘦物体,拉远一点,稍微抬高视角
|
||||
distanceFactor = 1.5;
|
||||
heightFactor = 0.4;
|
||||
angle = Math.PI / 6; // 30度
|
||||
} else if (size.x > size.y * 2) {
|
||||
// 扁平物体,降低视角
|
||||
heightFactor = 0.2;
|
||||
angle = Math.PI / 8; // 22.5度
|
||||
}
|
||||
const { position: cameraPos, target } = calculateCameraPosition(
|
||||
modelInfo,
|
||||
camera.value,
|
||||
{
|
||||
distanceFactor,
|
||||
heightFactor,
|
||||
angle
|
||||
}
|
||||
);
|
||||
// 执行相机动画
|
||||
animateCamera(
|
||||
camera.value.position,
|
||||
cameraPos,
|
||||
controls.value.target,
|
||||
target
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let openModel = (event:any)=>{
|
||||
@@ -61,39 +91,43 @@ let openModel = (event:any)=>{
|
||||
return intersects
|
||||
}
|
||||
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: 1000, // 新的相机位置z
|
||||
x2: 0, // 新的控制中心点位置x
|
||||
y2: target2.y, // 新的控制中心点位置x
|
||||
duration:1,
|
||||
ease:'linear',
|
||||
onUpdate:()=>{
|
||||
camera.value.position.x = options.x1;
|
||||
camera.value.position.y = options.y1;
|
||||
camera.value.position.z = options.z1;
|
||||
controls.value.target.x = options.x2;
|
||||
controls.value.target.y = options.y2;
|
||||
// controls.value.target.z = object.z2;
|
||||
controls.value.update();
|
||||
},
|
||||
onComplete:()=>{
|
||||
isTweening = false
|
||||
}
|
||||
// z2: target2.z // 新的控制中心点位置x
|
||||
})
|
||||
|
||||
function animateCamera(
|
||||
startCameraPos: THREE.Vector3,
|
||||
endCameraPos: THREE.Vector3,
|
||||
startTarget: THREE.Vector3,
|
||||
endTarget: THREE.Vector3
|
||||
) {
|
||||
if (isTweening) return;
|
||||
isTweening = true;
|
||||
|
||||
let options = {
|
||||
cx: startCameraPos.x,
|
||||
cy: startCameraPos.y,
|
||||
cz: startCameraPos.z,
|
||||
tx: startTarget.x,
|
||||
ty: startTarget.y,
|
||||
tz: startTarget.z
|
||||
};
|
||||
|
||||
gsap.to(options, {
|
||||
cx: endCameraPos.x,
|
||||
cy: endCameraPos.y,
|
||||
cz: endCameraPos.z,
|
||||
tx: endTarget.x,
|
||||
ty: endTarget.y,
|
||||
tz: endTarget.z,
|
||||
duration: 1,
|
||||
ease: 'power2.inOut', // 使用更自然的缓动
|
||||
onUpdate: () => {
|
||||
camera.value.position.set(options.cx, options.cy, options.cz);
|
||||
controls.value.target.set(options.tx, options.ty, options.tz);
|
||||
controls.value.update();
|
||||
},
|
||||
onComplete: () => {
|
||||
isTweening = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
const setModel = async (url:any)=>{
|
||||
clearModel(group,scene)
|
||||
@@ -118,7 +152,7 @@ const open = async ()=>{
|
||||
// composer.render();
|
||||
};
|
||||
animate();
|
||||
await setModel("https://www.minio-api.aida.com.hk/aida-threed/female/glb/1%E7%9F%AD%E8%A2%96T%E6%81%A4.glb?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260310%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260310T032933Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=184ec7f9ff3076dde5aca66e2d2e27f8d180add698a5b1040fe903a55cb2f85e")
|
||||
await setModel(threeGlb)
|
||||
load.value.state = false
|
||||
}
|
||||
|
||||
|
||||
@@ -57,21 +57,25 @@ export const initThree = (threeDom)=>{
|
||||
DirectionalLight 平行光,比如太阳光
|
||||
SpotLight 聚光源
|
||||
*/
|
||||
const pointLight = new THREE.DirectionalLight(0xffffff,.5);
|
||||
pointLight.intensity = 1.2
|
||||
pointLight.castShadow = true//开启阴影
|
||||
pointLight.shadow.mapSize = new THREE.Vector2(width, height)
|
||||
scene.add(pointLight); //点光源添加到场景中
|
||||
//设置环境光全亮
|
||||
const pointLight = new THREE.AmbientLight(0xffffff,.2);
|
||||
scene.add(pointLight);
|
||||
// const pointLight = new THREE.AmbientLight(0xffffff,1.0);
|
||||
// pointLight.intensity = 1.2//光源强度
|
||||
// pointLight.castShadow = true//开启阴影
|
||||
// pointLight.shadow.mapSize = new THREE.Vector2(width, height)
|
||||
// scene.add(pointLight); //点光源添加到场景中
|
||||
// pointLight.position.set(400, 200, 300); //点光源位置
|
||||
pointLight.position.y = 400;
|
||||
pointLight.position.z = 200;
|
||||
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;
|
||||
// pointLight.position.y = 100;
|
||||
// pointLight.position.z = 50;
|
||||
// pointLight.position.x = 100;
|
||||
|
||||
// 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;
|
||||
// scene.add(floorMesh);
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
// const texture = textureLoader.load('/3dModel/sketch-thick.jpg');
|
||||
@@ -85,41 +89,144 @@ export const clearModel = (group,scene)=>{
|
||||
scene.value.remove(oldGroup);
|
||||
}
|
||||
|
||||
|
||||
// 计算模型包围盒
|
||||
export const getModelInfo = (model: THREE.Object3D) => {
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const maxSize = Math.max(size.x, size.y, size.z);
|
||||
return {
|
||||
box,
|
||||
center,
|
||||
size,
|
||||
maxSize
|
||||
};
|
||||
};
|
||||
// 根据模型信息计算相机位置
|
||||
export const calculateCameraPosition = (
|
||||
modelInfo: ReturnType<typeof getModelInfo>,
|
||||
camera: THREE.PerspectiveCamera,
|
||||
options?: {
|
||||
distanceFactor?: number; // 距离系数,默认1.5
|
||||
heightFactor?: number; // 高度偏移系数,默认0.3
|
||||
angle?: number; // 观察角度,默认正前方
|
||||
}
|
||||
) => {
|
||||
const { center, size, maxSize } = modelInfo;
|
||||
const fov = camera.fov * (Math.PI / 180);
|
||||
|
||||
const distanceFactor = options?.distanceFactor ?? 1.5;
|
||||
const heightFactor = options?.heightFactor ?? 0.3;
|
||||
|
||||
// 计算合适的相机距离
|
||||
const distance = (maxSize / 2) / Math.tan(fov / 2) * distanceFactor;
|
||||
|
||||
// 根据角度计算相机位置
|
||||
const angle = options?.angle ?? 0; // 0表示正前方
|
||||
|
||||
return {
|
||||
position: new THREE.Vector3(
|
||||
center.x + distance * Math.sin(angle),
|
||||
center.y + size.y * heightFactor,
|
||||
center.z + distance * Math.cos(angle)
|
||||
),
|
||||
target: center.clone(),
|
||||
distance,
|
||||
center,
|
||||
size
|
||||
};
|
||||
};
|
||||
// 根据模型信息计算光源位置
|
||||
export const calculateLightPosition = (
|
||||
modelInfo: ReturnType<typeof getModelInfo>,
|
||||
options?: {
|
||||
xFactor?: number; // X轴偏移系数,默认0.5
|
||||
yFactor?: number; // Y轴偏移系数,默认0.8
|
||||
zFactor?: number; // Z轴偏移系数,默认0.5
|
||||
}
|
||||
) => {
|
||||
const { center, size } = modelInfo;
|
||||
|
||||
const xFactor = options?.xFactor ?? 0.5;
|
||||
const yFactor = options?.yFactor ?? 0.8;
|
||||
const zFactor = options?.zFactor ?? 0.5;
|
||||
|
||||
return new THREE.Vector3(
|
||||
center.x + size.x * xFactor,
|
||||
center.y + size.y * yFactor,
|
||||
center.z + size.z * zFactor
|
||||
);
|
||||
};
|
||||
export const addModel = async (
|
||||
url:any,
|
||||
controls:OrbitControls,
|
||||
camera:THREE.PerspectiveCamera,
|
||||
pointLight:THREE.DirectionalLight,
|
||||
group:THREE.Group,
|
||||
load: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());
|
||||
controls.value.target.copy(center);
|
||||
// controls.autoRotate = true
|
||||
camera.value.position.y = center.y;
|
||||
camera.value.position.z = 1000;
|
||||
pointLight.value.position.y = 250;
|
||||
pointLight.value.position.z = 1250;
|
||||
group.value.add(scene);
|
||||
resolve('')
|
||||
},(xhr:any) => { // 加载进度回调
|
||||
const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2);
|
||||
load.value.progress = percent
|
||||
// updateProgressBar(Number(percent));
|
||||
},(error:any) => { // 加载失败回调
|
||||
console.error('模型加载失败:', error);
|
||||
reject('')
|
||||
})
|
||||
})
|
||||
url: any,
|
||||
controls: OrbitControls,
|
||||
camera: THREE.PerspectiveCamera,
|
||||
pointLight: THREE.DirectionalLight,
|
||||
group: THREE.Group,
|
||||
load: any
|
||||
) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
var fbxLoader = new GLTFLoader();
|
||||
let drac = new DRACOLoader()
|
||||
drac.setDecoderPath('/draco/')
|
||||
fbxLoader.setDRACOLoader(drac)
|
||||
|
||||
fbxLoader.load(url,
|
||||
(obj: any) => {
|
||||
let scene = obj.scene;
|
||||
scene.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
// 如果是基础材质,转换为标准材质
|
||||
if (child.material instanceof THREE.MeshBasicMaterial) {
|
||||
const oldMat = child.material;
|
||||
child.material = new THREE.MeshStandardMaterial({
|
||||
map: oldMat.map,
|
||||
color: oldMat.color,
|
||||
roughness: 0.4,
|
||||
metalness: 0
|
||||
});
|
||||
}
|
||||
// 如果是标准材质,调整粗糙度
|
||||
else if (child.material instanceof THREE.MeshStandardMaterial) {
|
||||
child.material.roughness = 0.4;
|
||||
child.material.metalness = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 获取模型信息
|
||||
const modelInfo = getModelInfo(scene);
|
||||
const { position: cameraPos, target } = calculateCameraPosition(
|
||||
modelInfo,
|
||||
camera.value,
|
||||
{
|
||||
distanceFactor: 1.5,
|
||||
heightFactor: 0.3,
|
||||
angle: 0 // 正前方
|
||||
}
|
||||
);
|
||||
// 设置相机位置
|
||||
camera.value.position.copy(cameraPos);
|
||||
// 设置控制器目标点
|
||||
controls.value.target.copy(target);
|
||||
// 计算并设置光源位置
|
||||
const lightPos = calculateLightPosition(modelInfo);
|
||||
pointLight.value.position.copy(lightPos);
|
||||
// 将模型添加到场景
|
||||
group.value.add(scene);
|
||||
// 可选:将模型信息存储在模型上,方便后续使用
|
||||
(scene as any).userData.modelInfo = modelInfo;
|
||||
|
||||
resolve('')
|
||||
},
|
||||
(xhr: any) => { // 加载进度回调
|
||||
const percent = xhr.total == 0 ? 100 : (xhr.loaded / xhr.total * 100).toFixed(2);
|
||||
load.value.progress = percent
|
||||
console.log('模型加载进度:', percent);
|
||||
},
|
||||
(error: any) => { // 加载失败回调
|
||||
console.error('模型加载失败:', error);
|
||||
reject('')
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user