首页>>前端>>JavaScript->Vue3+ThreeJS实现3D机械臂控制和预览

Vue3+ThreeJS实现3D机械臂控制和预览

时间:2023-12-01 本站 点击:0

一、效果预览

PC端

移动端

二、技术栈

web端使用Vue3+element-plus UI

3D显示使用three.js

三、过程

新建Vue3项目

清除无关的新手引导代码

安装vue-router 4

安装three.js

新建src/views/home/index.vue 为主页

新建src/layout/index.vue layout主页

新建src/layout/components/Menu组件

新建src/layout/components/Robot3d组件

三、核心代码

使用three.js构建3D机械臂

除去必要的3D场景元素外,initRobot方法是构建机械臂的核心

总共有活动关节5个分别是:D1~D5

总共有力臂4个分别是:B1~B4

根据结构从D1开始嵌套为子节点

最终渲染生成力臂

其中的setRobotRotation方法对外提供角度控制

其中的setControlsEnabled方法对外提供视角开个控制

// 文件路径:src/views/home/components/Robot3d/manager/BaseManager.jsimport * as dat from "dat.gui";import * as THREE from "three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";export default class baseManager {    constructor(canvas) {        //Gui        this.gui = new dat.GUI();        this.gui.hide();        //Canvas        this.canvas = canvas        //Sizes        this.sizes = {}        //Camera        this.camera = null        //Renderer        this.renderer = null        // Scene        this.scene = new THREE.Scene();        //AnimateTick        this.clock = new THREE.Clock();        this.previousTime = 0;        this.initWindowSizes()        this.initcamera()        this.inLights()        this.initHelper()        this.initControls()        this.initRobot()        this.initRenderer()        this.initAnimateTick()    }    /**     * Sizes     */    initWindowSizes() {        /**         * Sizes         */        const sizes = {            width: this.canvas.parentNode.clientWidth,            height: this.canvas.parentNode.clientHeight,        };        window.addEventListener("resize", () => {            // Update sizes            sizes.width = this.canvas.parentNode.clientWidth;            sizes.height = this.canvas.parentNode.clientHeight;            // Update camera            this.camera.aspect = sizes.width / sizes.height;            this.camera.updateProjectionMatrix();            // Update renderer            this.renderer.setSize(sizes.width, sizes.height);            this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));        });        this.sizes = sizes;    }    /**     * Camera     */    initcamera() {        // Base camera        const camera = new THREE.PerspectiveCamera(            75,            this.sizes.width / this.sizes.height,            0.1,            100        );        camera.position.set(0, 4, 10);        this.scene.add(camera);        this.camera = camera;    }    /**     * inLights     */    inLights() {        const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);        this.scene.add(ambientLight);        const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);        directionalLight.castShadow = true;        directionalLight.shadow.mapSize.set(1024, 1024);        directionalLight.shadow.camera.far = 25;        directionalLight.shadow.camera.left = -7;        directionalLight.shadow.camera.top = 7;        directionalLight.shadow.camera.right = 7;        directionalLight.shadow.camera.bottom = -7;        directionalLight.position.set(5, 5, 5);        this.scene.add(directionalLight);    }    /**     * Helper     */    initHelper() {        const axes = new THREE.AxesHelper(20);        this.scene.add(axes);        const gridHelper = new THREE.GridHelper(100, 100);        this.scene.add(gridHelper);    }    /**     * Controls     */    initControls() {        const controls = new OrbitControls(this.camera, this.canvas);        controls.target.set(0, 0.75, 0);        controls.enableDamping = true;        this.controls = controls;    }    setControlsEnabled(enabled) {        this.controls.enabled = enabled    }    /**     * Robot     */    initRobot() {        const D1 = new THREE.Mesh(            new THREE.CylinderGeometry(1, 1, 0.5, 32),            new THREE.MeshStandardMaterial({                color: "#E45826",                metalness: 0,                roughness: 0.5,            })        );        this.gui.add(D1.rotation, "y").min(-Math.PI).max(Math.PI).step(0.01);        const D2 = new THREE.Mesh(            new THREE.SphereGeometry(0.5, 32, 16),            new THREE.MeshStandardMaterial({                color: "#1B1A17",                metalness: 0,                roughness: 0.5,            })        );        D2.position.set(0, 0.75, 0);        // D2.rotation.z = Math.PI / 4;        D1.add(D2);        this.gui.add(D2.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);        const B1 = new THREE.Mesh(            new THREE.BoxGeometry(0.5, 3, 0.5),            new THREE.MeshStandardMaterial({                color: "#E45826",                metalness: 0,                roughness: 0.5,            })        );        B1.position.set(-1.5 * Math.sin(Math.PI / 4), 1, 0);        B1.rotation.z = Math.PI / 4;        D2.add(B1);        const D3 = new THREE.Mesh(            new THREE.SphereGeometry(0.5, 32, 16),            new THREE.MeshStandardMaterial({                color: "#1B1A17",                metalness: 0,                roughness: 0.5,            })        );        D3.position.set(0, 1.5, 0);        // D3.rotation.z = -Math.PI / 2;        B1.add(D3);        this.gui.add(D3.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);        const B2 = new THREE.Mesh(            new THREE.BoxGeometry(0.5, 3, 0.5),            new THREE.MeshStandardMaterial({                color: "#E45826",                metalness: 0,                roughness: 0.5,            })        );        B2.position.set(1.5, 0, 0);        B2.rotation.z = -Math.PI / 2;        D3.add(B2);        const D4 = new THREE.Mesh(            new THREE.SphereGeometry(0.5, 32, 16),            new THREE.MeshStandardMaterial({                color: "#1B1A17",                metalness: 0,                roughness: 0.5,            })        );        D4.position.set(0, 1.5, 0);        // D4.rotation.z = -Math.PI / 4;        B2.add(D4);        this.gui.add(D4.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);        const B3 = new THREE.Mesh(            new THREE.BoxGeometry(0.5, 3, 0.5),            new THREE.MeshStandardMaterial({                color: "#E45826",                metalness: 0,                roughness: 0.5,            })        );        // B3.position.set(0, 1.5, 0);        B3.position.set(1.5 * Math.cos(Math.PI / 4), 1.5 * Math.sin(Math.PI / 4), 0);        B3.rotation.z = -Math.PI / 4;        D4.add(B3);        const D5 = new THREE.Mesh(            new THREE.SphereGeometry(0.5, 32, 16),            new THREE.MeshStandardMaterial({                color: "#1B1A17",                metalness: 0,                roughness: 0.5,            })        );        D5.position.set(0, 1.5, 0);        // D5.rotation.z = -Math.PI / 2;        B3.add(D5);        this.gui.add(D5.rotation, "z").min(-Math.PI).max(Math.PI).step(0.01);        const B4 = new THREE.Mesh(            new THREE.BoxGeometry(0.5, 1, 0.5),            new THREE.MeshStandardMaterial({                color: "#E45826",                metalness: 0,                roughness: 0.5,            })        );        B4.position.set(0.5, 0, 0);        B4.rotation.z = -Math.PI / 2;        D5.add(B4);        D1.castShadow = true;        D2.castShadow = true;        B1.castShadow = true;        D3.castShadow = true;        B2.castShadow = true;        D4.castShadow = true;        B3.castShadow = true;        D5.castShadow = true;        B4.castShadow = true;        this.scene.add(D1);        this.D1 = D1        this.D2 = D2        this.D3 = D3        this.D4 = D4        this.D5 = D5    }    setRobotRotation(rotation, name, direction) {        this[name].rotation[direction] = rotation    }    /**     * Renderer     */    initRenderer() {        this.renderer = new THREE.WebGLRenderer({            canvas: this.canvas,        });        this.renderer.shadowMap.enabled = true;        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;        this.renderer.setSize(this.sizes.width, this.sizes.height);        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));        // this.renderer.setClearColor("#fff");    }    /**     * AnimateTick     */    initAnimateTick() {        const elapsedTime = this.clock.getElapsedTime();        const deltaTime = elapsedTime - this.previousTime;        this.previousTime = elapsedTime;        //Update controls        this.controls.update();        // Render        this.renderer.render(this.scene, this.camera);        // Call tick again on the next frame        window.requestAnimationFrame(() => {            this.initAnimateTick()        });    }}

home/components/Robot3d/index.vue实现3D场景的呈现

调用BaseManager类完成渲染

并且定义控制角度和视角开关的方法

// 路径:src/views/home/components/Robot3d/index.vue<template>  <canvas class="webgl" ref="webgl"></canvas></template><script setup>import { defineExpose, onMounted } from "vue";import BaseManager from "./manager/BaseManager.js";let base = null;onMounted(() => {  base = new BaseManager(document.querySelector("canvas.webgl"));});const setRobotRotation = (e, name, direction) => {  base.setRobotRotation(e, name, direction);};const setControlsEnabled = (enabled) => {  base.setControlsEnabled(enabled);};defineExpose({ setRobotRotation, setControlsEnabled });</script><style scscope>.webgl {  width: 100%;  height: 100%;  outline: none;}</style>

home/components/Menu/index.vue实现控制界面

// 路径:src/views/home/components/Menu/index.vue<template>  <el-scrollbar height="100%">    <div class="slider-block">      <div class="slider-item">        <span class="demonstration">鼠标视角控制器</span>        <el-switch v-model="mouseValue" @change="switchChange" />      </div>      <div class="slider-item">        <span class="demonstration">关节一(绕Y轴旋转)</span>        <el-slider          v-model="value1"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D1', 'y')"        />      </div>      <div class="slider-item">        <span class="demonstration">关节二(绕Z轴旋转)</span>        <el-slider          v-model="value2"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D2', 'z')"        />      </div>      <div class="slider-item">        <span class="demonstration">关节三(绕Z轴旋转)</span>        <el-slider          v-model="value3"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D3', 'z')"        />      </div>      <div class="slider-item">        <span class="demonstration">关节四(绕Z轴旋转)</span>        <el-slider          v-model="value4"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D4', 'z')"        />      </div>      <div class="slider-item">        <p class="demonstration">关节五</p>        <span class="demonstration">绕x轴旋转</span>        <el-slider          v-model="value5_1"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D5', 'x')"        />        <span class="demonstration">绕y轴旋转</span>        <el-slider          v-model="value5_2"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D5', 'y')"        />        <span class="demonstration">绕Z轴旋转</span>        <el-slider          v-model="value5_3"          show-input          :min="min"          :max="max"          :step="0.01"          @input="sliderInput($event, 'D5', 'z')"        />      </div>    </div>  </el-scrollbar></template><script setup>import { ref, defineEmits } from "vue";const mouseValue = ref(true);const value1 = ref(0);const value2 = ref(0);const value3 = ref(0);const value4 = ref(0);const value5_1 = ref(0);const value5_2 = ref(0);const value5_3 = ref(0);const min = ref(Number(-Math.PI.toFixed(2)));const max = ref(Number(Math.PI.toFixed(2)));const emit = defineEmits(["sliderInput", "switchChange"]);const sliderInput = (e, name, direction) => {  emit("sliderInput", e, name, direction);};const switchChange = (e) => {  emit("switchChange", e);};</script><style scope>.slider-block {  padding: 20px 10px;}.slider-item {  margin: 20px 0;}.demonstration {  margin: 0 10px 10px 0;}</style>

引入home页的两个自定义组件

// 路径:src/views/home/index.vue<template>  <el-container>    <el-drawer v-model="drawer" direction="ltr" size="100%">      <el-aside>        <Menu @sliderInput="sliderInput" @switchChange="switchChange" />      </el-aside>    </el-drawer>    <el-main>      <div class="btn" v-show="!drawer">        <el-button          type="primary"          :icon="Operation"          circle          size="large"          @click="drawerSwitch"        />      </div>      <Robot3d ref="Robot3dRef" />    </el-main>  </el-container></template><script setup>import { ref } from "vue";import Menu from "./components/Menu/index.vue";import Robot3d from "./components/Robot3d/index.vue";import { Operation } from "@element-plus/icons-vue";const Robot3dRef = ref();const sliderInput = (e, name, direction) => {  Robot3dRef.value.setRobotRotation(e, name, direction);};const switchChange = (e) => {  Robot3dRef.value.setControlsEnabled(e);};const drawer = ref(false);const drawerSwitch = () => {  drawer.value = !drawer.value;};</script><style scscope>.el-aside {  width: 100%;  background-color: #304156;  color: #eee;}.el-overlay {  max-width: 450px;}.el-drawer__header {  margin: 0;  background-color: #304156;  color: #eee;}.el-drawer__body {  background-color: #304156;  padding: 0;}.el-container {  height: 100%;}.el-main {  padding: 0;  margin: 0;  overflow: hidden;  outline: none;}.btn {  position: fixed;  bottom: 5%;  left: 50%;  transform: translateX(-50%);}</style>

通过上述核心代码就能构建机械臂控制和预览

四、源码地址

点击查看源码

五、视频预览

本文演示视频:点击浏览

更多前端内容欢迎关注公众号:天小天个人网

原文:https://juejin.cn/post/7098350437723865124


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/6398.html