当前位置: 首页 > news >正文

Cannon-es.js之HingeConstraint铰链约束案例

本文目录

  • 前言
  • 最终效果
    • 推力效果
    • 马达效果
  • 1、HingeConstraint
    • 1.1 概念
    • 1.2 功能特点
    • 1.3 使用场景
  • 2、前置代码准备
    • 2.1 代码
    • 2.2 效果
  • 3、HingeConstraint的使用
    • 3.1 代码
    • 3.2 效果
    • 3.3 点击生成力
    • 3.4 马达效果
  • 4、完整代码

前言

在3D物理引擎的领域中,cannon-es以其高效、轻量级和易于集成的特性,成为了WebGL环境中实现逼真物理模拟的重要工具。今天,我们将聚焦于cannon-es中的HingeConstraint,这一强大的铰链约束功能。
HingeConstraint允许我们模拟两个刚体之间的铰链连接,从而实现复杂的旋转和交互效果。无论是模拟门的开关、杠杆的运作,还是创建机械臂的关节,HingeConstraint都能提供精确且高效的物理模拟。
在本文中,我们将通过前置代码准备、HingeConstraint的具体使用以及完整代码展示,带你领略这一约束的神奇魅力。我们还将探索如何通过点击生成力以及添加马达效果,进一步丰富物理模拟的交互性和趣味性。无论你是物理引擎的新手,还是经验丰富的开发者,本文都将为你提供一份详尽的指南,帮助你更好地掌握cannon-es中的HingeConstraint

最终效果

推力效果

请添加图片描述

马达效果

请添加图片描述

1、HingeConstraint

1.1 概念

HingeConstraint,即铰链约束,在cannon-es物理引擎中用于模拟两个刚体之间的铰链连接。这种约束允许两个刚体在一定范围内相对旋转,类似于现实中的门轴或铰链。

1.2 功能特点

  1. 旋转限制:HingeConstraint可以限制两个刚体之间的相对旋转角度,从而模拟出类似于门、杠杆等物体的运动效果。
  2. 力传递:通过铰链约束,可以将力从一个刚体传递到另一个刚体,实现复杂的物理交互效果。
  3. 参数可调:在Cannon-es.js中,HingeConstraint提供了多个参数来调整约束的行为,如旋转轴、旋转范围、摩擦力等。

1.3 使用场景

  1. 门和窗:模拟门或窗的开启和关闭效果,通过调整旋转轴和旋转范围来实现。
  2. 杠杆和吊桥:模拟杠杆或吊桥的运动效果,通过铰链约束实现物体的平衡和旋转。
  3. 机械臂:在机器人或机械臂的模拟中,使用铰链约束来连接各个关节,实现复杂的运动轨迹。

2、前置代码准备

2.1 代码

<template><canvas ref="cannonDemo" class="cannonDemo"></canvas>
</template><script setup>
import { onMounted, ref } from "vue"
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const cannonDemo = ref('null')onMounted(() => {const cannonDemoDomWidth = cannonDemo.value.offsetWidthconst cannonDemoDomHeight = cannonDemo.value.offsetHeight// 创建场景const scene = new THREE.Scene// 创建相机const camera = new THREE.PerspectiveCamera( // 透视相机45, // 视角 角度数cannonDemoDomWidth / cannonDemoDomHeight, // 宽高比 占据屏幕0.1, // 近平面(相机最近能看到物体)1000, // 远平面(相机最远能看到物体))camera.position.set(0, 2, 30)// 创建渲染器const renderer = new THREE.WebGLRenderer({antialias: true, // 抗锯齿canvas: cannonDemo.value})// 设置设备像素比renderer.setPixelRatio(window.devicePixelRatio)// 设置画布尺寸renderer.setSize(cannonDemoDomWidth, cannonDemoDomHeight)const light = new THREE.AmbientLight(0x404040, 200); // 柔和的白光scene.add(light);let meshes = []let phyMeshes = []const physicsWorld = new CANNON.World()// 设置y轴重力physicsWorld.gravity.set(0, -9.82, 0)const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody =  new CANNON.Body({shape: boxShape,mass: 0,type: CANNON.BODY_TYPES.STATIC,position: new CANNON.Vec3(0, 5, 0)})physicsWorld.addBody(boxBody)phyMeshes.push(boxBody)const boxGeometry = new THREE.BoxGeometry(1, 5, 5)const boxMaterial = new THREE.MeshBasicMaterial({color: 0xff0000})const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)scene.add(boxMesh)meshes.push(boxMesh)const boxShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody1 =  new CANNON.Body({shape: boxShape1,position: new CANNON.Vec3(0, -1, 0)})physicsWorld.addBody(boxBody1)phyMeshes.push(boxBody1)const boxGeometry1 = new THREE.BoxGeometry(1, 5, 5)const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0x0000ff, wireframe: true})const boxMesh1 = new THREE.Mesh(boxGeometry1, boxMaterial1)scene.add(boxMesh1)meshes.push(boxMesh1)const axesHelper = new THREE.AxesHelper(30);scene.add(axesHelper);const updatePhysic = () => { // 因为这是实时更新的,所以需要放到渲染循环动画animate函数中physicsWorld.step(1 / 60)for (let i = 0; i < phyMeshes.length; i++) {meshes[i].position.copy(phyMeshes[i].position)meshes[i].quaternion.copy(phyMeshes[i].quaternion)}}// 控制器const control = new OrbitControls(camera, renderer.domElement)// 开启阻尼惯性,默认值为0.05control.enableDamping = true// 渲染循环动画function animate() {// 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)requestAnimationFrame(animate)updatePhysic()// 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用control.update()renderer.render(scene, camera)}// 执行动画animate()
})</script>
<style scoped>
.cannonDemo {width: 100vw;height: 100vh;
}
</style>

2.2 效果

请添加图片描述

可以看到我准备了两个方块,因为两个方块之间没有任何约束所以导致蓝色方块掉下去了,所以我们接下来在其中间添加铰链。


3、HingeConstraint的使用

3.1 代码

    // 创建铰链约束const hingeConstraint = new CANNON.HingeConstraint(boxBody, boxBody1, {pivotA: new CANNON.Vec3(0, -3, 0),pivotB: new CANNON.Vec3(0, 3, 0),axisA: new CANNON.Vec3(0, 0, 1),axisB: new CANNON.Vec3(0, 0, 1),collideConnected: true // 两个物体是否会碰撞})physicsWorld.addConstraint(hingeConstraint)

3.2 效果

请添加图片描述
可以看到,我们添加铰链后蓝色线框方块被拉住不会往下掉。

3.3 点击生成力

我们添加点击事件,点一下就给一个x轴正方的力,代码如下:

    window.addEventListener('click', () => {// 点击创建力const force = new CANNON.Vec3(100,0,0)// 应用力boxBody1.applyForce(force, boxBody1.position)})

效果如下:
请添加图片描述
由于我们设置了collideConnected: true // 两个物体是否会碰撞所以蓝色线框方块不能够继续往上旋转。

3.4 马达效果

我们将collideConnected: false // 两个物体是否会碰撞设置成false,或者直接删除这行代码默认就是false,我们再添加马达效果的代码如下:

    window.addEventListener('click', () => {// 点击创建力// const force = new CANNON.Vec3(100,0,0)// // 应用力// boxBody1.applyForce(force, boxBody1.position)// 启用马达hingeConstraint.enableMotor()// 设置马达速度hingeConstraint.setMotorSpeed(10)})

效果如下:
请添加图片描述
可以看到我们点击后蓝色线框方块开始了旋转。我们可以利用这个做成车轮在转动的效果。


4、完整代码

最后给出完整代码如下:

<template><canvas ref="cannonDemo" class="cannonDemo"></canvas>
</template><script setup>
import { onMounted, ref } from "vue"
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const cannonDemo = ref('null')onMounted(() => {const cannonDemoDomWidth = cannonDemo.value.offsetWidthconst cannonDemoDomHeight = cannonDemo.value.offsetHeight// 创建场景const scene = new THREE.Scene// 创建相机const camera = new THREE.PerspectiveCamera( // 透视相机45, // 视角 角度数cannonDemoDomWidth / cannonDemoDomHeight, // 宽高比 占据屏幕0.1, // 近平面(相机最近能看到物体)1000, // 远平面(相机最远能看到物体))camera.position.set(0, 2, 30)// 创建渲染器const renderer = new THREE.WebGLRenderer({antialias: true, // 抗锯齿canvas: cannonDemo.value})// 设置设备像素比renderer.setPixelRatio(window.devicePixelRatio)// 设置画布尺寸renderer.setSize(cannonDemoDomWidth, cannonDemoDomHeight)const light = new THREE.AmbientLight(0x404040, 200); // 柔和的白光scene.add(light);let meshes = []let phyMeshes = []const physicsWorld = new CANNON.World()// 设置y轴重力physicsWorld.gravity.set(0, -9.82, 0)const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody =  new CANNON.Body({shape: boxShape,mass: 0,type: CANNON.BODY_TYPES.STATIC,position: new CANNON.Vec3(0, 5, 0)})physicsWorld.addBody(boxBody)phyMeshes.push(boxBody)const boxGeometry = new THREE.BoxGeometry(1, 5, 5)const boxMaterial = new THREE.MeshBasicMaterial({color: 0xff0000})const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)scene.add(boxMesh)meshes.push(boxMesh)const boxShape1 = new CANNON.Box(new CANNON.Vec3(0.5, 2.5, 2.5))const boxBody1 =  new CANNON.Body({shape: boxShape1,mass: 1,position: new CANNON.Vec3(0, -1, 0)})physicsWorld.addBody(boxBody1)phyMeshes.push(boxBody1)const boxGeometry1 = new THREE.BoxGeometry(1, 5, 5, 5, 5)const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0x0000ff, wireframe: true})const boxMesh1 = new THREE.Mesh(boxGeometry1, boxMaterial1)scene.add(boxMesh1)meshes.push(boxMesh1)// 创建铰链约束const hingeConstraint = new CANNON.HingeConstraint(boxBody, boxBody1, {pivotA: new CANNON.Vec3(0, -3, 0),pivotB: new CANNON.Vec3(0, 3, 0),axisA: new CANNON.Vec3(0, 0, 1),axisB: new CANNON.Vec3(0, 0, 1),collideConnected: false // 两个物体是否会碰撞})physicsWorld.addConstraint(hingeConstraint)window.addEventListener('click', () => {// 点击创建力// const force = new CANNON.Vec3(100,0,0)// // 应用力// boxBody1.applyForce(force, boxBody1.position)// 启用马达hingeConstraint.enableMotor()// 设置马达速度hingeConstraint.setMotorSpeed(10)})const axesHelper = new THREE.AxesHelper(30);scene.add(axesHelper);const updatePhysic = () => { // 因为这是实时更新的,所以需要放到渲染循环动画animate函数中physicsWorld.step(1 / 60)for (let i = 0; i < phyMeshes.length; i++) {meshes[i].position.copy(phyMeshes[i].position)meshes[i].quaternion.copy(phyMeshes[i].quaternion)}}// 控制器const control = new OrbitControls(camera, renderer.domElement)// 开启阻尼惯性,默认值为0.05control.enableDamping = true// 渲染循环动画function animate() {// 在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)requestAnimationFrame(animate)updatePhysic()// 更新控制器。如果没在动画里加上,那必须在摄像机的变换发生任何手动改变后调用control.update()renderer.render(scene, camera)}// 执行动画animate()
})</script>
<style scoped>
.cannonDemo {width: 100vw;height: 100vh;
}
</style>

在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

相关文章:

  • leetcode163.缺失的区间,模拟
  • 【算法】堆排之LCR 159.库存管理 Ⅲ(easy)
  • Python Web 与量子计算
  • css的页面布局属性
  • 65.【C语言】联合体
  • Databend 实现高效实时查询:深入解读 Dictionary 功能
  • 对于基础汇编的趣味认识
  • 综合练习 学习案例
  • 【AIGC】ChatGPT提示词助力自媒体内容创作升级
  • 笔记本电脑如何改ip地址:操作指南与注意事项
  • 深度解析:Python蓝桥杯青少组精英赛道与高端题型概览
  • 程序设计语言
  • JavaScript模块化-CommonJS规范和ESM规范
  • 论文阅读(十一):CBAM: Convolutional Block Attention Module
  • C++入门(有C语言基础)
  • angular2开源库收集
  • Asm.js的简单介绍
  • canvas绘制圆角头像
  • CentOS 7 修改主机名
  • HashMap剖析之内部结构
  • HomeBrew常规使用教程
  • Java 多线程编程之:notify 和 wait 用法
  • java2019面试题北京
  • Laravel Telescope:优雅的应用调试工具
  • leetcode46 Permutation 排列组合
  • node学习系列之简单文件上传
  • PHP的Ev教程三(Periodic watcher)
  • Python_网络编程
  • scrapy学习之路4(itemloder的使用)
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • unity如何实现一个固定宽度的orthagraphic相机
  • ViewService——一种保证客户端与服务端同步的方法
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 前端技术周刊 2019-02-11 Serverless
  • 如何在 Tornado 中实现 Middleware
  • 深度学习入门:10门免费线上课程推荐
  • 使用common-codec进行md5加密
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 由插件封装引出的一丢丢思考
  • Java数据解析之JSON
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (1)bark-ml
  • (3)(3.5) 遥测无线电区域条例
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (ZT)一个美国文科博士的YardLife
  • (四)软件性能测试
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (转)我也是一只IT小小鸟
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • // an array of int