效果:淡入,靠近物体
代码:
<template>
<div>
<el-container>
<el-main>
<div class="box-card-left">
<div id="threejs" style="border: 1px solid red"></div>
<div class="box-right">
<el-button type="primary" @click="lookFor('设备A')"
>设备A</el-button
>
<el-button type="primary" @click="lookFor('设备B')"
>设备B</el-button
>
<el-button type="primary" @click="lookAll">整体</el-button>
<el-button type="primary" @click="saveImg">保存图片</el-button>
</div>
</div>
</el-main>
</el-container>
</div>
</template>
<script>
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import TWEEN from "@tweenjs/tween.js";
export default {
data() {
return {
scene: null,
camera: null,
renderer: null,
mesh: null,
geometry: null,
group: null,
material: null,
clock: null,
mixer: null,
};
},
created() {},
mounted() {
this.name = this.$route.query.name;
this.init();
// 监听点击事件
this.addClickEventListener();
},
methods: {
goBack() {
this.$router.go(-1);
},
init() {
// 创建场景对象
this.scene = new this.$three.Scene();
this.group = new this.$three.Group();
this.createMesh({
x: 50,
y: 50,
z: 50,
name: "设备A",
});
this.createMesh({
x: -50,
y: 50,
z: 50,
name: "设备B",
});
this.scene.add(this.group);
const axesHelper = new this.$three.AxesHelper(150);
this.scene.add(axesHelper);
// 创建环境光对象
const ambientLight = new this.$three.AmbientLight(0xffffff);
this.scene.add(ambientLight);
// 创建相机对象
this.camera = new this.$three.PerspectiveCamera();
this.camera.position.set(300, 300, 300);
this.camera.lookAt(0, 0, 0);
// 创建渲染器对象
this.renderer = new this.$three.WebGLRenderer({
preserveDrawingBuffer: true, // 把画布内容保存为图片时,需要设置为true
});
this.renderer.setSize(1000, 800);
this.renderer.render(this.scene, this.camera);
window.document
.getElementById("threejs")
.append(this.renderer.domElement);
// 创建相机空间轨道控制器对象
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.addEventListener("change", () => {
this.renderer.render(this.scene, this.camera);
console.log(
" this.camera.position",
this.camera.position.x,
this.camera.position.y,
this.camera.position.z
);
});
},
// 创建网格模型的方法
createMesh(obj) {
// 创建立方缓冲几何体对象
const geometry = new this.$three.BoxGeometry(obj.x, obj.y, obj.z);
// 创建材质对象
const material = new this.$three.MeshLambertMaterial({
color: this.randomColor(),
transparent: true,//开启透明计算
opacity: 0.0//完全透明
});
const mesh = new this.$three.Mesh(geometry, material);
mesh.position.set(obj.x, obj.y, obj.z);
mesh.name = obj.name;
if (this.group) {
this.group.add(mesh);
}
},
lookFor(name) {
if (this.scene && this.scene.getObjectByName(name)) {
// 通过 getObjectByName() 方法获取name为设备A的模型
const equipment_A = this.scene.getObjectByName(name);
// 创建Vector3类型的位置对象
const position = new this.$three.Vector3();
// 获取设置A的世界坐标并赋值到position对象中
equipment_A.getWorldPosition(position);
// 向量x,y,z坐标值在position的基础上增加50,
const position_scalar = position.clone().addScalar(100);
// 创建TWEEN对象并调用Tween方法
new TWEEN.Tween({
x: this.camera.position.x,
y: this.camera.position.y,
z: this.camera.position.z,
px: this.controls.target.x,
py: this.controls.target.y,
pz: this.controls.target.z,
opacity: equipment_A.material.opacity//完全透明
})
.to(
{
x: position_scalar.x,
y: position_scalar.y,
z: position_scalar.z,
px: equipment_A.position.x,
py: equipment_A.position.y,
pz: equipment_A.position.z,
opacity: 1.0
},
3000
)
.onUpdate((obj) => {
// 设置相机位置
this.camera.position.set(obj.x, obj.y, obj.z);
// 设置控制器指向
this.controls.target.set(obj.px, obj.py, obj.pz);
// 更新控制器
this.controls.update();
equipment_A.material.opacity = obj.opacity;
})
.onComplete(obj => {
equipment_A.material.transparent = false;
})
.start()
.easing(TWEEN.Easing.Sinusoidal.InOut); //使用二次缓动函数;
/**
easing()语法格式
* // easing函数:缓动算法(运动效果)
// easing类型:定义缓动算法起作用地方
tween.easing(TWEEN.Easing.easing函数.easing类型);
// easing类型 In , Out , InOut
Linear:默认效果可以不设置,可以理解为没有加速过程直接进入匀速状态,或者说没有减速过程,直接刹车
Quadratic:二次方的缓动(t^2)
Cubic:三次方的缓动(t^3)
Quartic:四次方的缓动(t^4)
Quintic:五次方的缓动(t^5)
Sinusoidal:正弦曲线的缓动(sin(t))
Exponential:指数曲线的缓动(2^t)启动非常慢,后面快
Circular:圆形曲线的缓动(sqrt(1-t^2))会有弹性衰减往复运动感
Elastic:指数衰减的正弦曲线缓动;TWEEN.Easing.Elastic.inout 会有弹性衰减往复运动感
Back:超过范围的三次方缓动((s+1)*t^3 – s*t^2)会有弹性衰减往复运动感
Bounce:指数衰减的反弹缓动。会有弹性衰减往复运动感
* */
this.loop();
}
},
loop() {
this.renderer.render(this.scene, this.camera);
TWEEN.update();
window.requestAnimationFrame(this.loop);
},
lookAll() {
/**
* 查看整体的思路:
* 用包围盒 Box3, 将场景中所有的模型包裹起来,计算出
* (box3.min.x + box.max.x) / 2 = centerX
* (box.min.y + box.max.y) / 2 = centerY
* (box.min.z + box.max.z) / 2 = centerZ
* , 计算出 centerX, centerY, centerZ 整体的中心坐标,
* 为了显示包围盒的边界,可以使用Box3Helper辅助对象;
* 相机的位置position要从当前位置定位到
*
* */
// 创建包围盒对象
const box3 = new this.$three.Box3();
// 设置包围盒中的对象
const groupBox = box3.expandByObject(this.group);
console.log(groupBox);
const box3Helper = new this.$three.Box3Helper(box3, 0xffffff);
this.scene.add(box3Helper);
let max_x = groupBox.max.x;
let max_y = groupBox.max.y;
let max_z = groupBox.max.z;
let min_x = groupBox.min.x;
let min_y = groupBox.min.y;
let min_z = groupBox.min.z;
let center_x = (max_x + min_x) / 2;
let center_y = (max_y + min_y) / 2;
let center_z = (max_z + min_z) / 2;
//
let increment_x =
Math.abs(max_x) > Math.abs(min_x) ? Math.abs(max_x) : Math.abs(min_y);
let increment_y =
Math.abs(max_y) > Math.abs(min_y) ? Math.abs(max_y) : Math.abs(min_y);
let increment_z =
Math.abs(max_z) > Math.abs(min_z) ? Math.abs(max_z) : Math.abs(min_z);
new TWEEN.Tween({
x: this.camera.position.x,
y: this.camera.position.y,
z: this.camera.position.z,
px: this.controls.target.x,
py: this.controls.target.y,
pz: this.controls.target.z,
})
.to(
{
x: center_x + increment_x * 2,
y: center_y + increment_y * 2,
z: center_z + increment_z * 2,
px: center_x,
py: center_y,
pz: center_z,
},
1200
)
.onUpdate((obj) => {
this.camera.position.set(obj.x, obj.y, obj.z);
this.controls.target.set(obj.px, obj.py, obj.pz);
this.controls.update();
})
.start();
this.loop();
},
saveImg() {
const link = document.createElement("a");
const canvas = this.renderer.domElement;
link.href = canvas.toDataURL("image/png");
link.download = "threejs.png";
link.click();
},
randomColor() {
const numbers = Array.from({ length: 255 }, (_, i) => i);
const color = [...numbers];
// 要生成min-max之间的随机数,公式为:Math.random()*(max-min+1)+min
let i = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
let j = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
let k = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
return new this.$three.Color("rgb(" + i + ", " + j + ", " + k + ")");
},
// 在canvas画布上添加监听点击的事件
addClickEventListener() {
// 获取id 是 threejs 的元素;
const dom = window.document.getElementById("threejs");
const canvasWidth = dom.clientWidth; // 获取元素的宽度
const canvasHeight = dom.clientHeight; // 获取元素的高度
dom.addEventListener("click", (e) => {
const x = e.offsetX; // 获取鼠标当前点击的点距离dom元素左上角原点 在x轴方向上的距离
const y = e.offsetY; // 获取鼠标当前点击的点距离dom元素左上角原点 在y轴方向上的距离
console.log(x, y);
// 由于canvas画布上的坐标值与普通2d页面的坐标值是不一样的;
// 在canvas画布上的坐标轴是以画布的中心点为原点,左右x轴,值 -1 ~ 1,,上下y轴,值-1 ~ 1;
// 坐标需要进行坐标转换
const pos_x = (x / canvasWidth) * 2 - 1; // 转换后的x坐标
const pos_y = -(y / canvasHeight) * 2 + 1; // 转换后的y坐标
// 创建射线投射器对象(可以在初始化方法中创建,每次点击时创建有些浪费资源)
const rayCaster = new this.$three.Raycaster();
// 计算射线(在点击位置创建一条射线,用来拾取模型对象)
rayCaster.setFromCamera(
new this.$three.Vector2(pos_x, pos_y),
this.camera
);
const mesh_list = [];
// traverse 是threejs中的递归遍历方法;找出group中的mesh
this.group.traverse((obj) => {
if (obj.isMesh) {
mesh_list.push(obj);
}
});
// 射线交叉计算(计算出与自身射线相交的网格模型)
const intersects = rayCaster.intersectObjects(mesh_list);
if (intersects && intersects.length > 0) {
console.log(intersects[0]);
this.lookFor(intersects[0].object.name);
}
});
},
},
};
</script>
//
<style lang="less" scoped>
.box-card-left {
display: flex;
align-items: flex-start;
flex-direction: row;
width: 100%;
.box-right {
img {
width: 500px;
user-select: none;
}
}
}
</style>