Files
ece.suwa3d.com/src/components/arFrame/backup.vue
T
2025-06-05 09:25:26 +08:00

456 lines
12 KiB
Vue

<template>
<div id="webGL3d" ref="container"></div>
<video ref="videoRef" id="video" autoplay playsinline></video>
</template>
<script lang="ts" setup>
import * as THREE from "three";
import "aframe";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { localStorage } from "@/utils/local-storage";
// 导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
const props = defineProps({
modelFile: { type: String, default: () => "" },
bgModel: { type: String, default: "" },
pid: { type: Number, default: 0 },
});
import { showSuccessToast, showToast } from "vant";
const container = ref(null);
const STATE = {
NONE: -1,
MOVE: 0,
ZOOM_OR_PAN: 1,
POSITION_MOVE: 1,
};
const mouseInfo = ref({
startX: 0,
startY: 0,
isDown: false,
startPointerDistance: 0,
state: STATE.NONE,
});
let model, scene, camera, renderer, controls;
const videoRef = ref();
const twoFirst = ref(true);
const lastPoint = ref({
touchStartX1: 0,
touchStartY1: 0,
touchStartX2: 0,
touchStartY2: 0,
});
const emit = defineEmits([
"handleProgress",
"handleLoaded",
]);
onMounted(() => {
isDevices();
});
// 监听组件销毁事件
onBeforeUnmount(() => {
// 销毁 Three.js 模型
scene.remove(model);
model.traverse((child) => {
if (child.isMesh) {
child.geometry.dispose();
child.material.dispose();
}
});
// 清理渲染器
renderer.dispose();
});
watch(
() => props.modelFile,
(val, oldVal) => {
if (val) {
// 加载模型
Init();
}
},
{
immediate: true,
}
);
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const Init = async () => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
// 创建场景
scene = new THREE.Scene();
// 创建一个包含模型的父对象
// 旋转缩放相关配置
// 创建相机
camera = new THREE.OrthographicCamera(
window.innerWidth / -10.5,
window.innerWidth / 10.5,
window.innerHeight / 10.5,
window.innerHeight / -10.5,
0.1,
1000
// 90,
// window.innerWidth / window.innerHeight,
// .1,
// 1000
);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);
// 从上方照射的白色平行光,强度为 0.5。
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
// directionalLight.castShadow = true;
directionalLight.position.x = 1;
directionalLight.position.y = 1;
directionalLight.position.z = 80;
directionalLight.target = modelContainer;
scene.add(directionalLight);
let renderParam = {
antialias: true, // true/false表示是否开启反锯齿
alpha: true, // true/false 表示是否可以设置背景色透明
precision: "highp", // highp/mediump/lowp 表示着色精度选择
premultipliedAlpha: false,
maxLights: 3,
};
// 创建渲染器
renderer = new THREE.WebGLRenderer(renderParam);
renderer.setSize(window.innerWidth, window.innerHeight);
// renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(window.devicePixelRatio); // 设置画布分辨率 提高画质
container.value.appendChild(renderer.domElement);
await startCamera();
var videoElement = document.getElementById("video");
var videoTexture = new THREE.VideoTexture(videoElement);
videoTexture.minFilter = THREE.LinearFilter;
scene.background = videoTexture;
// 加载hdr贴图
// let rgbeLoader = new RGBELoader();
// const rgbUrl = props.bgModel || "src/assets/bg.hdr";
// rgbeLoader.load(rgbUrl, envMap => {
// // 设置球形贴图
// envMap.mapping = THREE.EquirectangularReflectionMapping;
// // 设置环境贴图
// scene.background = envMap;
// scene.environment = envMap;
// })
// 创建点光源
var pointLight = new THREE.PointLight(0xffffff, 500); // 设置点光源的颜色和强度
pointLight.position.set(0, 0, 100); // 设置点光源的位置
scene.add(pointLight);
// 相机位置
camera.position.z = 200;
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0);
// 监听缩放\旋转操作
renderer.domElement.addEventListener("touchstart", (event) => {
console.log("touchstart", event);
handleTouchStart(event);
});
const handleTouchStart = (event) => {
mouseInfo.value.isDown = true;
// 双指
const touch0 = event.touches[0];
const touch1 = event.touches[1];
// 单点触控
if (event.touches.length === 1) {
mouseInfo.value.startX = touch0.pageX;
mouseInfo.value.startY = touch0.pageY;
mouseInfo.value.state = STATE.MOVE; // 这边换成rorate会比较好点
} else if (event.touches.length === 2) {
// 双指,计算两指距离
// 一般是平移或者缩放
const dx = touch0.pageX - touch1.pageX;
const dy = touch0.pageY - touch1.pageY;
mouseInfo.value.startPointerDistance = Math.sqrt(dx * dx + dy * dy);
mouseInfo.value.startX = (touch0.pageX + touch1.pageX) / 2;
mouseInfo.value.startY = (touch0.pageY + touch1.pageY) / 2;
mouseInfo.value.state = STATE.ZOOM_OR_PAN;
}
};
renderer.domElement.addEventListener("touchmove", (event) => {
if(isModel) {
handleTouchMove(event);
}
event.stopPropagation();
event.preventDefault();
}, { passive: false });
renderer.domElement.addEventListener('click', (event) => {
onMouseClick(event)
});
function onMouseClick(event) {
var mouse = new THREE.Vector2();
// 计算鼠标点击位置的归一化设备坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
if(isModel.value) {
modelContainer.position.x = mouse.x * 40;
modelContainer.position.y = mouse.y * 100;
}else {
loadModel(mouse);
}
}
const isModel = ref(false); // 是否创建模型
function loadModel(position) {
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader);
loader.load(
props.modelFile,
(gltf) => {
// 加载成功后的回调函数
emit("handleLoaded");
model = gltf.scene;
// model.position.set(0, 0, 0);
const box = new THREE.Box3().setFromObject(model);
// sizeBox.expandByObject(gltf.scene)
// sizeBox.getSize(v3)
// console.log("gltf", gltf)
// console.log("volume", volume)
// 获取包围盒中心点
const center = box.getCenter(new THREE.Vector3());
model.position.sub(center); // 将模型位置移到原点处
// 将模型添加到父对象中
modelContainer.add(model);
// modelGeometry.center(); // 这个等同于上面这行代码
// model.translateX(-80)
scene.add(modelContainer);
modelContainer.position.x = position.x * 40;
modelContainer.position.y = position.y * 100;
console.log("modelContainer", modelContainer.position)
isModel.value = true;
},
(xhr) => {
// 加载过程中的回调函数
emit("handleProgress", Math.floor((xhr.loaded / xhr.total) * 100));
},
(error) => {
// 加载失败的回调函数
console.error("Failed to load model", error);
}
);
}
// 开始触碰
const handleTouchMove = (event) => {
if (!mouseInfo.value.isDown) {
return;
}
switch (mouseInfo.value.state) {
case STATE.MOVE:
if (event.touches.length === 1) {
handleRotate(event);
} else if (event.touches.length === 2) {
// 兼容处理,如果是移动过程中出现双指时,需要取消单指,然后再重新计算
renderer.domElement.removeEventListener(
"touchmove",
handleTouchMove,
false
);
renderer.domElement.removeEventListener(
"touchend",
handleTouchEnd,
false
);
handleTouchStart(event);
}
break;
case STATE.ZOOM_OR_PAN:
if (event.touches.length === 1) {
} else if (event.touches.length === 2) {
handleZoomOrPan(event);
}
break;
default:
break;
}
};
let rotateSpeed = 0.01;
// 模型旋转
const handleRotate = (event) => {
const x = event.touches[0].pageX;
const y = event.touches[0].pageY;
const { startX, startY } = mouseInfo.value;
const deltaX = x - startX;
const deltaY = y - startY;
modelContainer.rotation.y += deltaX * rotateSpeed;
modelContainer.rotation.x += deltaY * rotateSpeed;
mouseInfo.value.startX = x;
mouseInfo.value.startY = y;
};
// 模型缩放
const handleZoomOrPan = (event) => {
let { touchStartX1, touchStartY1, touchStartX2, touchStartY2 } =
lastPoint.value;
let initialScale = modelContainer.scale.x;
const touch0 = event.touches[0];
const touch1 = event.touches[1];
const dx = touch0.pageX - touch1.pageX;
const dy = touch0.pageY - touch1.pageY;
const distance = Math.sqrt(dx * dx + dy * dy);
let obj = {
touchStartX1: touch0.pageX,
touchStartY1: touch0.pageY,
touchStartX2: touch1.pageX,
touchStartY2: touch1.pageY,
};
if (twoFirst.value) {
twoFirst.value = false;
lastPoint.value = { ...obj };
} else {
let deltaScale =
initialScale * (distance / mouseInfo.value.startPointerDistance);
// 限制缩放距离
if (deltaScale < 0.25) {
deltaScale = 0.25;
} else if (deltaScale > 2) {
deltaScale = 2;
}
mouseInfo.value.startPointerDistance = distance;
modelContainer.scale.set(deltaScale, deltaScale, deltaScale);
const avgX = (touch0.pageX + touch1.pageX) / 2;
const avgY = (touch0.pageY + touch1.pageY) / 2;
const deltaX = avgX - (touchStartX1 + touchStartX2) / 2;
const deltaY = avgY - (touchStartY1 + touchStartY2) / 2;
modelContainer.position.x += deltaX * 0.3;
modelContainer.position.y -= deltaY * 0.3;
lastPoint.value = {
touchStartX1: touch0.pageX,
touchStartY1: touch0.pageY,
touchStartX2: touch1.pageX,
touchStartY2: touch1.pageY,
};
}
};
const handleTouchEnd = () => {
mouseInfo.value.isDown = false;
mouseInfo.value.state = STATE.NONE;
twoFirst.value = true;
lastPoint.value.touchStartX1 = 0;
lastPoint.value.touchStartY1 = 0;
lastPoint.value.touchStartX2 = 0;
lastPoint.value.touchStartY2 = 0;
// 取消移动事件监听
renderer.domElement.removeEventListener(
"touchmove",
handleTouchMove,
false
);
renderer.domElement.removeEventListener(
"touchstart",
handleTouchStart,
false
);
};
renderer.domElement.addEventListener("touchend", (event) => {
handleTouchEnd();
});
// 渲染场景
const animate = () => {
// controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
videoTexture.needsUpdate = true;
};
animate();
// 监听窗口缩放
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
};
const isDevices = () => {
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.oGetUserMedia;
if (!getUserMedia) {
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
};
const cameraStream = ref(null)
const startCamera = async () => {
isDevices();
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: { exact: "environment" },
},
});
cameraStream.value = stream;
videoRef.value.srcObject = stream;
} catch (error) {
console.error(error);
}
};
</script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>