安装:
javascript"># three.js
npm install --save three
附中文网:
5. gltf不同文件形式(.glb) | Three.js中文网
附官网:
安装 – three.js docs
完整代码(简易demo):
javascript"><template>
<div class="siteInspection" ref="threeContainer">
<div class="measurement-buttons">
<button @click="startDistanceMeasurement">测距</button>
<button @click="startAreaMeasurement">测面积</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const lzModel = ref("/lz.glb"); // 此处为你的 模型文件 .glb或 .gltf
const threeContainer = ref(null);
// 创建场景、相机和渲染器
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let model: THREE.Group | null = null;
let controls: OrbitControls;
let raycaster: THREE.Raycaster;
let mouse: THREE.Vector2;
let selectedObject: THREE.Mesh | null = null; // 用于跟踪当前选中的对象
let line: THREE.Line | null = null; // 用于存储高亮线
let label: THREE.Sprite | null = null; // 用于存储标签
// 测量相关变量
let isDistanceMeasuring = false;
let isAreaMeasuring = false;
let distancePoints: THREE.Vector3[] = [];
let areaPoints: THREE.Vector3[] = [];
let distanceLine: THREE.Line | null = null;
let areaLines: THREE.Line[] = [];
let distanceLabel: THREE.Sprite | null = null;
let areaLabel: THREE.Sprite | null = null;
let distanceDots: THREE.Mesh[] = [];
let areaDots: THREE.Mesh[] = [];
// 初始化
function initThree() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff); // 设置背景颜色为亮色
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
threeContainer.value.appendChild(renderer.domElement);
// 添加光源
const ambientLight = new THREE.AmbientLight(0x404040); // 环境光
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// 设置相机位置
camera.position.z = 5;
camera.lookAt(0, 0, 0);
// 创建GLTF加载器对象
const loader = new GLTFLoader();
loader.load(lzModel.value, function (gltf: any) {
model = gltf.scene;
scene.add(model);
});
// 初始化OrbitControls
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果
controls.dampingFactor = 0.25; // 阻尼系数
controls.enableZoom = true; // 启用缩放
controls.enablePan = true; // 启用平移
// 初始化Raycaster和Vector2
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// 渲染循环
function animate() {
requestAnimationFrame(animate);
controls.update(); // 更新OrbitControls
renderer.render(scene, camera);
}
animate();
// 清理事件监听器
onUnmounted(() => {
controls?.dispose();
threeContainer.value?.removeEventListener("mousedown", onDocumentMouseDown);
if (line) {
scene.remove(line);
}
if (label) {
scene.remove(label);
}
if (distanceLine) {
scene.remove(distanceLine);
}
areaLines.forEach((areaLine) => {
scene.remove(areaLine);
});
if (distanceLabel) {
scene.remove(distanceLabel);
}
if (areaLabel) {
scene.remove(areaLabel);
}
distanceDots.forEach((dot) => {
scene.remove(dot);
});
areaDots.forEach((dot) => {
scene.remove(dot);
});
});
// 添加鼠标点击事件监听
threeContainer.value.addEventListener("mousedown", onDocumentMouseDown);
}
function onDocumentMouseDown(event: MouseEvent) {
// 将鼠标位置归一化到-1到1之间
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线投射器
raycaster.setFromCamera(mouse, camera);
// 计算交点
const intersects = raycaster.intersectObjects(
model ? model.children : [],
true
);
if (intersects.length > 0) {
const intersect = intersects[0];
const point = intersect.point;
if (isDistanceMeasuring) {
if (event.button === 0) {
// 左键点击
distancePoints.push(point);
addDot(point, distanceDots);
if (distancePoints.length === 2) {
calculateDistance();
}
} else if (event.button === 2) {
// 右键点击
cancelDistanceMeasurement();
}
} else if (isAreaMeasuring) {
if (event.button === 0) {
// 左键点击
areaPoints.push(point);
addDot(point, areaDots);
if (areaPoints.length >= 3) {
updateAreaLines();
// calculateArea();
}
} else if (event.button === 2) {
// 右键点击
if (areaPoints.length >= 3) {
calculateArea();
} else {
cancelAreaMeasurement();
}
}
} else {
// 原有的点击选中功能
if (selectedObject) {
(selectedObject.material as THREE.MeshStandardMaterial).emissive.set(
0x000000
); // 恢复 emissive 颜色为黑色
if (line) {
scene.remove(line);
line = null;
}
if (label) {
scene.remove(label);
label = null;
}
}
if (intersect.object instanceof THREE.Mesh) {
(intersect.object.material as THREE.MeshStandardMaterial).emissive.set(
0x16d46b
); // 设置 emissive 颜色为绿色
selectedObject = intersect.object; // 更新选中的对象
// 创建线段
const startPoint = intersect.point;
const endPoint = startPoint.clone().add(new THREE.Vector3(0, 0, 0.5)); // 延伸到外部
const geometry = new THREE.BufferGeometry().setFromPoints([
startPoint,
endPoint,
]);
const material = new THREE.LineBasicMaterial({
color: 0x16d46b,
});
line = new THREE.Line(geometry, material);
scene.add(line);
// 创建标签
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context) {
const textWidth = context.measureText(intersect.object.name).width;
canvas.width = textWidth + 20;
canvas.height = 50;
context.font = "16px";
// context.fillStyle = "rgba(0,0,0,0.8)";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#0179d4";
context.textAlign = "center";
context.fillText(intersect.object.name, canvas.width / 2, 30);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true; // 确保纹理更新
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
label = new THREE.Sprite(spriteMaterial);
label.position.copy(endPoint);
label.scale.set(0.1, 0.1, 1); // 调整标签大小
scene.add(label);
}
}
}
} else {
// 如果没有点击到任何对象,恢复当前选中的对象的颜色并移除线和标签
if (selectedObject) {
(selectedObject.material as THREE.MeshStandardMaterial).emissive.set(
0x000000
); // 恢复 emissive 颜色为黑色
selectedObject = null; // 清除选中的对象
if (line) {
scene.remove(line);
line = null;
}
if (label) {
scene.remove(label);
label = null;
}
}
}
}
function startDistanceMeasurement() {
isDistanceMeasuring = true;
isAreaMeasuring = false;
distancePoints = [];
distanceDots.forEach((dot) => {
scene.remove(dot);
});
distanceDots = [];
if (distanceLine) {
scene.remove(distanceLine);
distanceLine = null;
}
if (distanceLabel) {
scene.remove(distanceLabel);
distanceLabel = null;
}
}
function startAreaMeasurement() {
isAreaMeasuring = true;
isDistanceMeasuring = false;
areaPoints = [];
areaDots.forEach((dot) => {
scene.remove(dot);
});
areaDots = [];
areaLines.forEach((areaLine) => {
scene.remove(areaLine);
});
areaLines = [];
if (areaLabel) {
scene.remove(areaLabel);
areaLabel = null;
}
}
function calculateDistance() {
const startPoint = distancePoints[0];
const endPoint = distancePoints[1];
const distance = startPoint.distanceTo(endPoint);
// 创建线段
const geometry = new THREE.BufferGeometry().setFromPoints([
startPoint,
endPoint,
]);
const material = new THREE.LineBasicMaterial({
color: 0x00ff00,
});
distanceLine = new THREE.Line(geometry, material);
scene.add(distanceLine);
// 创建标签
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context) {
const text = `距离: ${distance.toFixed(2)}`;
const textWidth = context.measureText(text).width;
canvas.width = textWidth + 25;
canvas.height = 35;
context.font = "12px Arial";
context.fillStyle = "#ffffff";
context.textAlign = "center";
context.fillText(text, canvas.width / 2, 15);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true; // 确保纹理更新
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
distanceLabel = new THREE.Sprite(spriteMaterial);
const midPoint = startPoint.clone().add(endPoint).divideScalar(2);
distanceLabel.position.copy(midPoint);
distanceLabel.scale.set(0.1, 0.1, 1); // 调整标签大小
scene.add(distanceLabel);
}
isDistanceMeasuring = false;
}
function calculateArea() {
let area = 0;
const numPoints = areaPoints.length;
for (let i = 0; i < numPoints; i++) {
const j = (i + 1) % numPoints;
area +=
areaPoints[i].x * areaPoints[j].y - areaPoints[j].x * areaPoints[i].y;
}
area = Math.abs(area) / 2;
// 创建标签
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context) {
const text = `面积: ${area.toFixed(2)}`;
const textWidth = context.measureText(text).width;
canvas.width = textWidth + 25;
canvas.height = 35;
context.font = "12px Arial";
context.fillStyle = "#ffffff";
context.textAlign = "center";
context.fillText(text, canvas.width / 2, 15);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true; // 确保纹理更新
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
areaLabel = new THREE.Sprite(spriteMaterial);
const centroid = new THREE.Vector3();
areaPoints.forEach((point) => {
centroid.add(point);
});
centroid.divideScalar(numPoints);
areaLabel.position.copy(centroid);
areaLabel.scale.set(0.1, 0.1, 1); // 调整标签大小
scene.add(areaLabel);
}
isAreaMeasuring = false;
}
function cancelDistanceMeasurement() {
isDistanceMeasuring = false;
distancePoints = [];
distanceDots.forEach((dot) => {
scene.remove(dot);
});
distanceDots = [];
if (distanceLine) {
scene.remove(distanceLine);
distanceLine = null;
}
if (distanceLabel) {
scene.remove(distanceLabel);
distanceLabel = null;
}
}
function cancelAreaMeasurement() {
isAreaMeasuring = false;
areaPoints = [];
areaDots.forEach((dot) => {
scene.remove(dot);
});
areaDots = [];
areaLines.forEach((areaLine) => {
scene.remove(areaLine);
});
areaLines = [];
if (areaLabel) {
scene.remove(areaLabel);
areaLabel = null;
}
}
// 添加点击圆点
function addDot(point: THREE.Vector3, dots: THREE.Mesh[]) {
const geometry = new THREE.SphereGeometry(0.01, 12, 12);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const dot = new THREE.Mesh(geometry, material);
dot.position.copy(point);
scene.add(dot);
dots.push(dot);
}
function updateAreaLines() {
areaLines.forEach((areaLine) => {
scene.remove(areaLine);
});
areaLines = [];
const numPoints = areaPoints.length;
for (let i = 0; i < numPoints; i++) {
const j = (i + 1) % numPoints;
const geometry = new THREE.BufferGeometry().setFromPoints([
areaPoints[i],
areaPoints[j],
]);
const material = new THREE.LineBasicMaterial({
color: 0x00ff00,
});
const line = new THREE.Line(geometry, material);
scene.add(line);
areaLines.push(line);
}
}
onMounted(() => {
initThree();
});
</script>
<style lang="scss" scoped>
.siteInspection {
width: 100%;
height: 100vh;
position: relative;
}
.measurement-buttons {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
</style>