threejs + urdf-loaders加载了一个urdf模型, 想实现模型上的模组,对应的模组变色. 并且点击的时候,获取到所点击的模组名称.模组是多个STL文件,urdf文件引入的link上写有对应的name.
模型目录结构:
-cart
---meshes
------base_link.STL
------lleft_back_link.STL
------lleft_forward_link.STL
------right_back_link.STL
------rigit_forward_link.STL
---urdf
------vehicle.urdf
文件无法上传, 我的urdf是在这个开源库下载的,可以直接访问下载.
https://gitee.com/reinovo/robot_urdf/tree/master/bobac2_description/urdf
// vue2代码
模板
<template>
<div class="page">
<div class="container-box">
<slider @sliderInput="sliderInput"/>
<div id="container"></div>
</div>
<button @click="saveFile()">保存图片</button>
</div>
</template>
<script>
import * as Three from 'three'
import {LoadingManager,MathUtils,} from 'three'
import URDFLoader from 'urdf-loader'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import Slider from './components/slider.vue'
/**
* 代表一个Three.js图形渲染组件
*/
export default {
name: 'HelloWorld',
components: {
Slider,
},
props: {
msg: String // 接收一个字符串类型的属性
},
data() {
return {
scene: null, // Three.js场景
camera: null, // Three.js相机
renderer: null, // Three.js渲染器
mesh: null, // Three.js网格
controls: null, // Three.js控制器
container: null, // 容器元素
handList : [],
circlePosition:'',
robot: null,
handConfig :[
{
name: '2.mtl',
rotation: {
x: 0.7,
y: 0.63,
z: 0,
},
},
{
name: '3.mtl',
rotation: {
x: 0.1,
y: 2.42,
z: 0,
},
},
{
name: '4.mtl',
rotation: {
x: 0.15,
y: 4.113,
z: 0,
},
},
{
name: '5.mtl',
rotation: {
x: 0.65,
y: 4.38,
z: 0,
},
},
{
name: '6.mtl',
rotation: {
x: 0.88,
y: 4.68,
z: 0,
},
},
],
}
},
methods: {
/**
* 初始化Three.js场景、相机、渲染器等
*/
init() {
this.container = document.getElementById('container');
this.scene = new Three.Scene();
const manager = new LoadingManager();
const loader = new URDFLoader(manager);
const path = '/modelFile/cart/urdf/vehicle.urdf';
loader.load(
path,
result => {
this.robot = result;
// 模型添加点击事件
document.addEventListener('click', (e) => {
this._testOnClick(e, this.robot)
})
// 设置ROBOT在坐标原点
this.robot.position.x = 0;
this.robot.position.y = 0;
// 确保模型底部位于渲染区域最下端(根据实际需求调整extraVerticalOffset)
this.robot.position.z = 0;
this.scene.add(this.robot);
}
)
this.renderer = new Three.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
// this.renderer.setClearColor(0xffffff,0.0); // 设置渲染器的背景颜色为白色且透明
this.container.appendChild(this.renderer.domElement);
this.createLight(-20, 30, 40); // 创建光源
this.createLight(20, -30, -40);
this.createCamera(); // 创建相机
this.createControls(); // 创建控制器
this.render(); // 进行渲染
},
/**
* 创建Three.js相机
*/
createCamera() {
this.camera = new Three.PerspectiveCamera(70, this.container.clientWidth / this.container.clientHeight, 0.01, 1000);
this.camera.position.set(0, -1, 0); // 设置相机位置
this.camera.lookAt(new Three.Vector3(0, 0, 0)); // 设置相机朝向
},
/**
* 创建轨道控制器
*/
createControls() {
this.clock = new Three.Clock(); // 创建计时器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.autoRotate = false // 设置是否自动旋转
},
/**
* 在场景中添加光源
* @param {number} pos_x 光源x轴位置
* @param {number} pos_y 光源y轴位置
* @param {number} pos_z 光源z轴位置
*/
createLight(pos_x, pos_y, pos_z) {
// 添加环境光
const ambientLight = new Three.AmbientLight(0x111111) // 创建环境光
this.scene.add(ambientLight) // 将环境光添加到场景
const directionLight = new Three.DirectionalLight(0xffffff)
directionLight.position.set(pos_x, pos_y, pos_z)
directionLight.intensity = 1.5
this.scene.add(directionLight) // 添加方向光
},
/**
* 实现渲染循环
*/
render() {
const delta = this.clock.getDelta(); // 获取自上次渲染以来的时间差
this.controls.update(delta); // 更新控制器
this.renderer.render(this.scene, this.camera); // 渲染场景
requestAnimationFrame(this.render); // 请求下一个动画帧
},
/**
* 保存当前渲染视图为图片
*/
saveFile() {
var link = document.createElement("a");
var canvas = document.getElementById('container').children[0];
var imgData = canvas.toDataURL({format: 'image/png', quality: 1, width: 20000, height: 4000});
var blob = this.dataURLtoBlob(imgData);
var objurl = URL.createObjectURL(blob);
link.download = "grid1.png";
link.href = objurl;
link.click();
},
/**
* 将data URL转换为Blob对象
* @param {string} dataurl - Data URL字符串
* @returns {Blob} 转换后的Blob对象
*/
dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {type: mime});
},
// 设置各个关节的角度
sliderInput(value, name) {
// console.log(value, name,this.robot.joints)
// 找到要设置的关节
name = "joint" + String(name);
this.robot?.joints[name].setJointValue(MathUtils.degToRad(value));
},
/**
* 当点击事件发生时,测试并处理点击交互。
* @param {MouseEvent} event - 触发的鼠标事件。
* @param {Object3D} object - 用于检测点击的对象。
*/
_testOnClick(event, object) {
// 将鼠标点击位置转换为标准设备坐标
const mouse = new Three.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
// 创建光线投射器用于检测鼠标点击是否命中3D对象
const raycaster = new Three.Raycaster();
// raycaster.params.Points.threshold = 10000;
// 设置光线投射器的起点(相机位置)和方向(基于鼠标点击位置)
raycaster.setFromCamera(mouse, this.camera);
// console.log(raycaster,'raycaster')
// 调整射线检测范围
// raycaster.far = 1000000000;
// 执行光线投射,检测是否有对象被命中
const intersects = raycaster.intersectObject(object, true);
console.log(intersects,'intersects',object)
// 如果命中了至少一个对象
if (intersects.length > 0) {
// 获取最接近的命中对象
const selected = intersects[0].object;
// 如果命中对象是一个网格,改变其材质为红色
if (selected instanceof Three.Mesh) {
console.log('命中',selected)
selected.material = new Three.MeshPhysicalMaterial({
color: 'red',
metalness: 1,
roughness: 0.7,
});
// // 获取并打印点击的模型名称或标识
// const modelName = selected.geometry?.userData?.filename?.split('.')[0];
// console.log('点击的模型名称:', modelName)
// if (selected.userData && selected.userData.modelName) {
// console.log('点击的模型名称:', selected.userData.modelName);
// } else if (selected.name) {
// console.log('点击的模型标识:', selected.name);
// } else {
// console.warn('无法获取点击模型的名称或标识,确保模型实例上已附加相关属性');
// }
}
}
},
},
mounted() {
this.init(); // 组件挂载时初始化Three.js场景
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.page{
width: 100%;
.container-box{
display: flex;
#container {
width: 60vw;
height: 90vh;
}
}
}
</style>