Initial commit

This commit is contained in:
natuka committed 2025-06-05 09:25:26 +08:00
commit 84061c9a18
243 files changed
+63148

No files matched your search

+456
View File
@@ -0,0 +1,456 @@
<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>
+591
View File
@@ -0,0 +1,591 @@
<template>
<div class="container">
<a-scene
xr-mode-ui="enabled: false"
arjs="sourceType: webcam;"
style="width: 100%; height: 100vh;"
@click="onSceneClick"
>
<a-camera position="0 1.6 0">
<a-cursor></a-cursor>
</a-camera>
<a-light type="directional" position="0 1 0.5" intensity="1"></a-light>
<a-light type="ambient" color="#ffffff"></a-light>
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="200" height="200" color="#7BC8A4" opacity="0.5"></a-plane>
<a-entity id="model" :gltf-model="modelFile" position="0 1 -.5" scale="0.005 0.005 0.005" ></a-entity>
</a-scene>
</div>
</template>
<script setup lang="ts">
import * as THREE from "three";
import "aframe";
// import '@/utils/threex/ar.min.js'
// import "@ar-js-org/ar.js"
// import * as THREEx from "@ar-js-org/ar.js/three.js/build/ar-threex"
import "@ar-js-org/ar.js/three.js/build/ar"
// import "@ar-js-org/ar.js/aframe/build/aframe-ar";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
// import {ARToolkit, ARController} from "@ar-js-org/artoolkit5-js"
let model, scene, camera, renderer, controls;
const container = ref(null);
var onRenderFcts = [];
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,
});
const twoFirst = ref(true);
const lastPoint = ref({
touchStartX1: 0,
touchStartY1: 0,
touchStartX2: 0,
touchStartY2: 0,
});
const emit = defineEmits(["rotateControl", "handleProgress", "handleLoaded"]);
const props = defineProps({
modelFile: { type: String, default: () => "" },
pid: { type: Number, default: 0 },
});
onMounted(() => {
isDevices();
document.addEventListener('click', onSceneClick, false);
// animate();
});
// 监听组件销毁事件
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();
test();
}
},
{ immediate: true }
);
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const test = () => {
}
const init = () => {
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);
// 创建场景
scene = new THREE.Scene();
// const {width, height} = 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
);
// 相机位置
camera.position.z = 200;
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0);
// 添加环境光
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);
// 加载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);
// 坐标轴
// const axesHelper = new THREE.AxesHelper(5)
// scene.add( axesHelper );
// // 添加轨道控制器
// controls = new OrbitControls(camera, renderer.domElement);
// controls.autoRotate = true;
// controls.autoRotateSpeed = 5;
runAR();
};
var arToolkitSource, arToolkitContext;
const runAR = () => {
// 创建AR场景和渲染器
arToolkitSource = new THREEx.ArToolkitSource({
sourceType: "webcam",
// sourceWidth: window.innerWidth,
// sourceHeight: window.innerHeight,
displayWidth: window.innerWidth,
displayHeight: window.innerHeight,
});
arToolkitContext = new THREEx.ArToolkitContext({
cameraParametersUrl: "src/assets/model/camera_para.dat",
detectionMode: "mono",
// canvasWidth: window.innerWidth,
// canvasHeight: window.innerHeight,
});
const markerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, { type: 'pattern', patternUrl: 'src/assets/model/patt.hiro' });
const marker = document.createElement('a-marker');
container.value.appendChild(marker);
arToolkitContext.init(function onCompleted() {
arToolkitSource.onResizeElement();
arToolkitSource.copyElementSizeTo(renderer.domElement);
camera.projectionMatrix.copy(arToolkitContext.getProjectionMatrix());
});
arToolkitSource.init(() => {
setTimeout(() => {
arToolkitContext.arController.setPatternDetectionMode(artoolkit.AR_TEMPLATE_MATCHING_MONO_AND_MATRIX);
}, 2000);
});
// arToolkitContext.addEventListener('artoolkit-mat', (event) => {
// var detectedPlanes = event.data;
// if (detectedPlanes.length > 0) {
// // 检测到平面,执行相应的操作
// var plane = detectedPlanes[0];
// console.log("Detected plane: ", plane);
// }
// });
// 创建AR追踪器
// var markerControls = new THREEx.ArMarkerControls(arToolkitContext, camera, {
// type: "pattern",
// patternUrl: "src/assets/model/pattern-marker.patt",
// });
// function onMarkerFound(event) {
// var markerRoot = event.data.markerRoot;
// // 获取平面的位置
// var position = new THREE.Vector3();
// markerRoot.matrixWorld.decompose(
// position,
// new THREE.Quaternion(),
// new THREE.Vector3()
// );
// // 添加标记物体
// addMarker(position);
// }
// markerControls.addEventListener("markerFound", onMarkerFound);
// function addMarker(position) {
// var markerGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
// var markerMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// var marker = new THREE.Mesh(markerGeometry, markerMaterial);
// marker.position.copy(position);
// scene.add(marker);
// }
// build a smoothedControls
// var smoothedRoot = new THREE.Group()
// scene.add(smoothedRoot)
// var smoothedControls = new THREEx.ArSmoothedControls(smoothedRoot, {
// lerpPosition: 0.4,
// lerpQuaternion: 0.3,
// lerpScale: 1,
// // minVisibleDelay: 1,
// // minUnvisibleDelay: 1,
// })
// onRenderFcts.push(function(delta){
// smoothedControls.update(markerRoot)
// })
// 加载模型
// loadModel(props.modelFile)
animate();
};
const loadModel = async (modelPath) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader);
loader.load(
modelPath,
(gltf) => {
// 加载成功后的回调函数
// emit("handleLoaded")
model = gltf.scene;
// model.position.set(0, 0, 0);
const box = new THREE.Box3().setFromObject(model);
let sizeBox = new THREE.Box3();
sizeBox.expandByObject(gltf.scene);
const v3 = new THREE.Vector3();
sizeBox.getSize(v3);
// console.log('size', 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);
},
(xhr) => {
// 加载过程中的回调函数
// emit("handleProgress",Math.floor((xhr.loaded / xhr.total) * 100))
},
(error) => {
// 加载失败的回调函数
console.error("Failed to load model", error);
}
);
// 监听缩放\旋转操作
renderer.domElement.addEventListener("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) => {
// console.log("touchmove",event )
handleTouchMove(event);
});
// 开始触碰
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;
// const theta = (x - startX) / radius * (-rotateSpeed)
// const phi = (y - startY) / radius * (-rotateSpeed)
// const maxRotationAngle = Math.PI / 2;
// // 没有变化那么停止
// if (Math.abs(theta) < .01 && Math.abs(phi) < .01) {
// return
// }
// // 移动位置判断
// if (Math.abs(x - startX) >= Math.abs(y - startY)) {
// // 旋转角度
// model.rotation.y -= theta
// } else {
// model.rotation.x -= phi
// model.rotation.x = Math.max(-maxRotationAngle, Math.min(maxRotationAngle, model.rotation.x))
// // this.gltfItemLights.rotation.x -= phi
// // this.gltfItemLights.rotation.x = Math.max(-maxRotationAngle, Math.min(maxRotationAngle, model.rotation.x));
// }
mouseInfo.value.startX = x;
mouseInfo.value.startY = y;
// console.log("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 < -2) {
deltaScale = -2;
} 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();
});
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 onSceneClick = (event) => {
const sceneEl = document.querySelector('a-scene');
let Camera = sceneEl.querySelector('[camera]');
let Model = sceneEl.querySelector('#model');
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, Camera.getObject3D('camera'));
const intersects = raycaster.intersectObjects([sceneEl.querySelector('#ground').object3D], true);
if (intersects.length > 0) {
const intersect = intersects[0];
console.log("--------onSceneClick-------",intersect)
Model.setAttribute('position', intersect.point);
Model.setAttribute('visible', 'true');
}
};
var lastTimeMsec = null;
// 渲染场景
const animate = () => {
// controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
// videoTexture.needsUpdate = true;
if (arToolkitSource.ready) {
arToolkitContext.update(arToolkitSource.domElement);
}
// measure time
// lastTimeMsec = lastTimeMsec || nowMsec-1000/60
// var deltaMsec = Math.min(200, nowMsec - lastTimeMsec)
// lastTimeMsec = nowMsec
// // call each update function
// onRenderFcts.forEach(function(onRenderFct){
// onRenderFct(deltaMsec/1000, nowMsec/1000)
// })
};
</script>
<style scoped>
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
body {
margin: 0;
overflow: hidden;
}
</style>
+13
View File
@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
+499
View File
@@ -0,0 +1,499 @@
<template>
<div ref="container" class="container">
<video ref="video" class="fullscreen-video" autoplay playsinline muted></video>
<canvas ref="canvas" class="fullscreen-canvas"></canvas>
</div>
</template>
<script setup lang="ts">
import * as tf from '@tensorflow/tfjs';
import * as cocoSsd from '@tensorflow-models/coco-ssd';
import * as THREE from "three";
// import "aframe";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
let model, scene, camera, renderer, controls;
const video = ref(null);
const canvas = ref(null);
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,
});
const twoFirst = ref(true);
const lastPoint = ref({
touchStartX1: 0,
touchStartY1: 0,
touchStartX2: 0,
touchStartY2: 0,
});
const emit = defineEmits(["handleProgress", "handleLoaded", "closeHint"]);
const props = defineProps({
modelFile: { type: String, default: () => "" },
pid: { type: Number, default: 0 },
});
onMounted(async() => {
await tf.setBackend('webgl'); // 或者'cpu',根据你的需求选择
await tf.ready();
const model = await cocoSsd.load();
const videoElement = video.value;
const canvasElement = canvas.value;
const context = canvasElement.getContext('2d');
const detectFrame = async () => {
const predictions = await model.detect(videoElement);
context.clearRect(0, 0, canvasElement.width, canvasElement.height);
context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
predictions.forEach((prediction) => {
if(prediction.class == "keyboard" && !isModel.value) {
loadModel(props.modelFile);
emit("closeHint")
}
// context.beginPath();
// context.rect(
// prediction.bbox[0],
// prediction.bbox[1],
// prediction.bbox[2],
// prediction.bbox[3]
// );
// context.lineWidth = 2;
// context.strokeStyle = 'green';
// context.fillStyle = 'green';
// context.stroke();
// context.fillText(
// prediction.class + ': ' + Math.round(prediction.score * 100) + '%',
// prediction.bbox[0],
// prediction.bbox[1] > 10 ? prediction.bbox[1] - 5 : 10
// );
});
requestAnimationFrame(detectFrame);
};
await setupCamera(videoElement);
resizeCanvas(canvasElement);
detectFrame();
window.addEventListener('resize', resizeCanvas);
});
// 监听组件销毁事件
onBeforeUnmount(() => {
// Clean up TensorFlow.js resources
if (model) {
model.dispose();
model = null;
}
tf.engine().disposeVariables();
console.log('TensorFlow.js resources disposed.');
});
const resizeCanvas = (canvasElement) => {
canvasElement.width = window.innerWidth;
canvasElement.height = window.innerHeight;
};
const setupCamera = async (videoElement) => {
isDevices();
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { exact: "environment" }, },
});
videoElement.srcObject = stream;
await new Promise((resolve) => {
videoElement.onloadedmetadata = () => {
resolve(videoElement);
};
});
};
watch(
() => props.modelFile,
(val, oldVal) => {
if (val) {
init();
}
},
{ immediate: true }
);
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const init = () => {
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);
// 创建场景
scene = new THREE.Scene();
// const {width, height} = 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
);
// 相机位置
camera.position.z = 200;
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0);
// 添加环境光
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);
// 加载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);
// 坐标轴
// const axesHelper = new THREE.AxesHelper(5)
// scene.add( axesHelper );
// // 添加轨道控制器
// controls = new OrbitControls(camera, renderer.domElement);
// controls.autoRotate = true;
// controls.autoRotateSpeed = 5;
};
const isModel = ref(false); // 是否创建模型
const loadModel = async (modelPath) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader);
loader.load(
modelPath,
(gltf) => {
// 加载成功后的回调函数
emit("handleLoaded")
model = gltf.scene;
// model.position.set(0, 0, 0);
const box = new THREE.Box3().setFromObject(model);
// 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);
isModel.value = true;
},
(xhr) => {
// 加载过程中的回调函数
emit("handleProgress",Math.floor((xhr.loaded / xhr.total) * 100))
},
(error) => {
// 加载失败的回调函数
console.error("Failed to load model", error);
}
);
// 监听缩放\旋转操作
renderer.domElement.addEventListener("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) => {
// console.log("touchmove",event )
handleTouchMove(event);
});
// 开始触碰
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();
});
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);
});
};
}
};
var lastTimeMsec = null;
// 渲染场景
const animate = () => {
// controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
// videoTexture.needsUpdate = true;
// if (arToolkitSource.ready) {
// arToolkitContext.update(arToolkitSource.domElement);
// }
// measure time
// lastTimeMsec = lastTimeMsec || nowMsec-1000/60
// var deltaMsec = Math.min(200, nowMsec - lastTimeMsec)
// lastTimeMsec = nowMsec
// // call each update function
// onRenderFcts.forEach(function(onRenderFct){
// onRenderFct(deltaMsec/1000, nowMsec/1000)
// })
};
</script>
<style lang="less" scoped>
body {
margin: 0;
overflow: hidden;
}
.container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.fullscreen-video, .fullscreen-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
object-fit: cover;
}
.fullscreen-video {
opacity: 0;
}
.fullscreen-canvas {
pointer-events: none; /* 避免画布阻挡视频的鼠标事件 */
}
</style>
+498
View File
@@ -0,0 +1,498 @@
<template>
<div ref="container" class="container">
<video ref="video" class="fullscreen-video" autoplay playsinline muted></video>
<canvas ref="canvas" class="fullscreen-canvas"></canvas>
</div>
</template>
<script setup lang="ts">
import * as tf from '@tensorflow/tfjs';
import * as cocoSsd from '@tensorflow-models/coco-ssd';
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
let model, scene, camera, renderer, controls;
const video = ref(null);
const canvas = ref(null);
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,
});
const twoFirst = ref(true);
const lastPoint = ref({
touchStartX1: 0,
touchStartY1: 0,
touchStartX2: 0,
touchStartY2: 0,
});
const emit = defineEmits(["handleProgress", "handleLoaded", "closeHint"]);
const props = defineProps({
modelFile: { type: String, default: () => "" },
pid: { type: Number, default: 0 },
});
onMounted(async() => {
await tf.setBackend('webgl'); // 或者'cpu',根据你的需求选择
await tf.ready();
const model = await cocoSsd.load();
const videoElement = video.value;
const canvasElement = canvas.value;
const context = canvasElement.getContext('2d');
const detectFrame = async () => {
const predictions = await model.detect(videoElement);
context.clearRect(0, 0, canvasElement.width, canvasElement.height);
context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
predictions.forEach((prediction) => {
if(prediction.class == "keyboard" && !isModel.value) {
loadModel(props.modelFile);
emit("closeHint")
}
// context.beginPath();
// context.rect(
// prediction.bbox[0],
// prediction.bbox[1],
// prediction.bbox[2],
// prediction.bbox[3]
// );
// context.lineWidth = 2;
// context.strokeStyle = 'green';
// context.fillStyle = 'green';
// context.stroke();
// context.fillText(
// prediction.class + ': ' + Math.round(prediction.score * 100) + '%',
// prediction.bbox[0],
// prediction.bbox[1] > 10 ? prediction.bbox[1] - 5 : 10
// );
});
requestAnimationFrame(detectFrame);
};
await setupCamera(videoElement);
resizeCanvas(canvasElement);
detectFrame();
window.addEventListener('resize', resizeCanvas);
});
// 监听组件销毁事件
onBeforeUnmount(() => {
// Clean up TensorFlow.js resources
if (model) {
model.dispose();
model = null;
}
tf.engine().disposeVariables();
console.log('TensorFlow.js resources disposed.');
});
const resizeCanvas = (canvasElement) => {
canvasElement.width = window.innerWidth;
canvasElement.height = window.innerHeight;
};
const setupCamera = async (videoElement) => {
isDevices();
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { exact: "environment" }, },
});
videoElement.srcObject = stream;
await new Promise((resolve) => {
videoElement.onloadedmetadata = () => {
resolve(videoElement);
};
});
};
watch(
() => props.modelFile,
(val, oldVal) => {
if (val) {
init();
}
},
{ immediate: true }
);
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const init = () => {
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);
// 创建场景
scene = new THREE.Scene();
// const {width, height} = 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
);
// 相机位置
camera.position.z = 200;
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0);
// 添加环境光
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);
// 加载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);
// 坐标轴
// const axesHelper = new THREE.AxesHelper(5)
// scene.add( axesHelper );
// // 添加轨道控制器
// controls = new OrbitControls(camera, renderer.domElement);
// controls.autoRotate = true;
// controls.autoRotateSpeed = 5;
};
const isModel = ref(false); // 是否创建模型
const loadModel = async (modelPath) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader);
loader.load(
modelPath,
(gltf) => {
// 加载成功后的回调函数
emit("handleLoaded")
model = gltf.scene;
// model.position.set(0, 0, 0);
const box = new THREE.Box3().setFromObject(model);
// 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);
isModel.value = true;
},
(xhr) => {
// 加载过程中的回调函数
emit("handleProgress",Math.floor((xhr.loaded / xhr.total) * 100))
},
(error) => {
// 加载失败的回调函数
console.error("Failed to load model", error);
}
);
// 监听缩放\旋转操作
renderer.domElement.addEventListener("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) => {
// console.log("touchmove",event )
handleTouchMove(event);
});
// 开始触碰
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();
});
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);
});
};
}
};
var lastTimeMsec = null;
// 渲染场景
const animate = () => {
// controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
// videoTexture.needsUpdate = true;
// if (arToolkitSource.ready) {
// arToolkitContext.update(arToolkitSource.domElement);
// }
// measure time
// lastTimeMsec = lastTimeMsec || nowMsec-1000/60
// var deltaMsec = Math.min(200, nowMsec - lastTimeMsec)
// lastTimeMsec = nowMsec
// // call each update function
// onRenderFcts.forEach(function(onRenderFct){
// onRenderFct(deltaMsec/1000, nowMsec/1000)
// })
};
</script>
<style lang="less" scoped>
body {
margin: 0;
overflow: hidden;
}
.container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.fullscreen-video, .fullscreen-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
object-fit: cover;
}
.fullscreen-video {
opacity: 0;
}
.fullscreen-canvas {
pointer-events: none; /* 避免画布阻挡视频的鼠标事件 */
}
</style>