效果演示

1.重叠检测


新生成的小球需要与数组中的每个小球进行判断是否重叠
判断依据:圆心距小于等于半径之和

2.碰撞检测

2.1 矢量分析


假设小球初速度方向为OA
经过碰撞以后它的速度为OA’
由于碰撞平面不是水平线,不方便计算,所以我们将整个球体模型沿碰撞平面旋转α角
旋转过后 OA --> OB OA’---->OB’
设OA = v
根据三角函数可得
x0 = v * cosα
y0 = v * sinβ
x1 = v * cos ( α + β)
= v * cosα * cosβ - v * sinα * sinβ
= x0 * cosα - y0 * sinα
y1 = v * sin ( α + β)
= v * sinα * cosβ + v * cosα + sinβ
= y0 * sinα + x0 * sinα

那么我们就可以获得 碰撞过后的速度
Vx’ = Vx * cosα- Vy * sinα
Vy’ = Vy * sinα+ Vx * sinα

    // 旋转向量function rotateVector(v, theta) {return {dx: v.dx * Math.cos(theta) - v.dy * Math.sin(theta),dy: v.dx * Math.cos(theta) + v.dy * Math.sin(theta)}}

2.2 旋转角度

现在我们需要计算的是旋转的 α 角度为多少
根据正切函数 tan α = y / x 可得 α = arctan * y /x
在Math函数中用 atan2 来计算,返回弧度,是指x轴距离点(y,x)与圆心连线的夹角
结果为正表示从X 轴逆时针旋转的角度
结果为负表示从X 轴顺时针旋转的角度
因为小球碰撞不外乎就两种情况

1.一低一高


向量等于终点坐标减去起始坐标
向量AB = (xa - xb,ya - yb)
tan θ = (ya-yb)/(xa-xb)
|θ| = arctan (ya-yb)/(xa-xb)
xa < xb ----> xa - xb <0
ya < yb ----> ya - yb <0
所以(ya-yb)/(xa-xb) > 0
由于碰撞平面是沿着顺指针方向旋转 -θ = atan2(y,x)
为了θ为正 进行取反
θ = -atan2(ya-yb)/(xa-xb)

2.一高一低


同理
xa < xb ----> xa - xb <0
ya > yb ----> ya - yb >0
所以(ya-yb)/(xa-xb) < 0
由于碰撞平面是沿着逆指针方向旋转 θ = - atan2(y,x)
所以
θ = -atan2(ya-yb)/(xa-xb)

然后我们就可以根据角度进行旋转

  let theta = -Math.atan2(p1.y - p2.y, p1.x - p2.x);

2.3 更新速度

根据动量守恒定律和动能定理我们有两个公式

将旋转过后的速度带入公式可得到新速度(旋转后)

在这里插入代码片

我们需要将新速度(旋转后)沿逆时针旋转α角,就可以获得小球
碰撞过后真正的速度

        // 完全弹性碰撞计算新的速度(旋转后的坐标)let v1RotatedAfterCollision = {dx: (v1Rotated.dx * (p1.mass - p2.mass) + 2 * p2.mass * v2Rotated.dx) / (p1.mass + p2.mass),dy: v1Rotated.dy};
​let v2RotatedAfterCollision = {dx: (v2Rotated.dx * (p2.mass - p1.mass) + 2 * p1.mass * v1Rotated.dx) / (p1.mass + p2.mass),dy: v2Rotated.dy};
​// 将新速度旋转回原来的角度获得真正的速度let v1finalCollision = rotateVector(v1RotatedAfterCollision,-theta);let v2finalCollision = rotateVector(v2RotatedAfterCollision,-theta);
​//更新小球的速度p1.dx = v1finalCollision.dx;p1.dy = v1finalCollision.dy;p2.dx = v2finalCollision.dx;p2.dy = v2finalCollision.dy;

2.4 修复漏洞

最后我们需要解决一个小bug
我们需要对碰撞检测进行一个优化
多个球黏在一起
原因:
两个小球碰撞的时候还没有分开,检测函数就已经执行了很多次,导致速度被更新了很多次
需要判断当两个小球靠近的时候才判断碰撞
如果两个小球已经往不同的方向运动了,就不需要对这两个小球进行检测了

建立坐标系


根据向量积公式可得cos (a,b) = a * b / (|a| * |b|)
cos θ = 向量p * 向量v /(|向量p| * |向量v| )
向量p * 向量v 的正负决定了θ的范围
当向量p * 向量v 为负时,θ > 180°
可以看出当θ > 180° 的时候 球B靠近球A,所以我们只需要判断向量p * 向量v是否小于0,小于则说明小球在靠近,需要进行计算新坐标

 if(xVelDiff * xDist + yVelDiff * yDist < 0){resolveCollision(this,p);}

懒人福利:

​<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><style>html,body {margin: 0;padding: 0;overflow: hidden;}
</style>
</head>
<body><canvas></canvas>
</body>
<script>var canvas = document.querySelector('canvas');canvas.width = window.innerWidth;canvas.height = window.innerHeight;let ctx = canvas.getContext('2d');let ballNums = 50;let colorArray = ['#4CBF88','#F2B134','#6F4A70','#FF6275','#00B5C4']
​window.addEventListener('resize', function (event) {canvas.width = window.innerWidth;canvas.height = window.innerHeightinit();})
​function randomIntFromRange(low, high) {return Math.floor(Math.random() * (high - low + 1) + low);}
​function randomFloatFromRange(low, high) {return Math.random() * (high - low + 1) + low;}
​// 产生随机颜色function randomColor(colors) {return colors[Math.floor(Math.random() * colors.length)];}
​// 两点之间距离function getDistance(x1, y1, x2, y2) {let dx = x1 - x2;let dy = y1 - y2;return Math.sqrt(dx * dx + dy * dy);}
​// 旋转向量function rotateVector(v, theta) {return {dx: v.dx * Math.cos(theta) - v.dy * Math.sin(theta),dy: v.dx * Math.cos(theta) + v.dy * Math.sin(theta)}}
​function resolveCollision(p1, p2) {let v1 = {dx: p1.dx,dy: p1.dy};let v2 = {dx: p2.dx,dy: p2.dy};
​//    旋转
​let theta = -Math.atan2(p1.y - p2.y, p1.x - p2.x);
​let v1Rotated = rotateVector(v1, theta);let v2Rotated = rotateVector(v2, theta);
​// 完全弹性碰撞计算新的速度(旋转后的坐标)let v1RotatedAfterCollision = {dx: (v1Rotated.dx * (p1.mass - p2.mass) + 2 * p2.mass * v2Rotated.dx) / (p1.mass + p2.mass),dy: v1Rotated.dy};
​let v2RotatedAfterCollision = {dx: (v2Rotated.dx * (p2.mass - p1.mass) + 2 * p1.mass * v1Rotated.dx) / (p1.mass + p2.mass),dy: v2Rotated.dy};
​// 将新速度旋转回原来的角度获得真正的速度let v1finalCollision = rotateVector(v1RotatedAfterCollision,-theta);let v2finalCollision = rotateVector(v2RotatedAfterCollision,-theta);
​//更新小球的速度p1.dx = v1finalCollision.dx;p1.dy = v1finalCollision.dy;p2.dx = v2finalCollision.dx;p2.dy = v2finalCollision.dy;
​}
​function Particle(x, y, mass, dx, dy, radius, color) {​this.x = x;this.y = y;this.mass = mass;this.dx = dx;this.dy = dy;this.radius = radius;this.color = color;
​this.draw = function () {ctx.beginPath();ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);ctx.fillStyle = this.color;ctx.fill();ctx.closePath();}
​this.update = function (ballArray) {​// 碰撞检测 for (let p of ballArray) {if (this === p) continue; //不要与进行自己比较if (getDistance(this.x, this.y, p.x, p.y) <= this.radius + p.radius) { let xVelDiff = p.dx - this.dx;let yVelDiff = p.dy - this.dy;let xDist = p.x - this.x;let yDist = p.y - this.y;
​if(xVelDiff * xDist + yVelDiff * yDist < 0){resolveCollision(this,p);}
​}}
​if (this.x + this.radius + this.dx > canvas.width ||this.x - this.radius + this.dx < 0) {this.dx = -this.dx;}if (this.y + this.radius + this.dy > canvas.height ||this.y - this.radius + this.dy < 0) {this.dy = -this.dy;}
​this.x += this.dx;this.y += this.dy;
​this.draw();
​}
​}
​let ballArray;
​function init() {ballArray = [];for (let i = 0; i < ballNums; i++) {let radius = randomIntFromRange(15, 20);let x = randomIntFromRange(radius, canvas.width - radius);let y = randomIntFromRange(radius, canvas.height - radius);//检测是否重叠  重叠则重新生成for (let j = 0; j < ballArray.length; j++) {if (getDistance(x, y, ballArray[j].x, ballArray[j].y) <= radius + ballArray[j].radius) {radius = randomIntFromRange(15, 20);x = randomIntFromRange(radius, canvas.width - radius);y = randomIntFromRange(radius, canvas.height - radius);j = -1;}}let mass = radius * 0.5;let dx = randomFloatFromRange(-1, 1);let dy = randomFloatFromRange(-1, 1);let color = randomColor(colorArray);ballArray.push(new Particle(x, y, mass, dx, dy, radius, color));}}
​function animate() {requestAnimationFrame(animate);ctx.fillStyle = "rgba(0, 0, 0, 0.25)";ctx.fillRect(0, 0,  canvas.width, canvas.height);for (let b of ballArray) {b.update(ballArray);}}
​init();animate();
</script>
​
</html>

悄悄告诉你,寒舞的github上还藏有好多好玩的东西哦!

前往寒舞小仓库

寒舞会不断更新更多有趣的特效和好玩的游戏,希望与大家一起成长

如果你觉得寒舞的代码对你有帮助的话,就点个Star吧 (っ•̀ω•́)っ ★⁾⁾

canvas 踩坑 * 小球弹性碰撞逻辑解析相关推荐

  1. STM32使用ADC+DMA进行多通道模拟量采集 (踩坑及傻瓜式解析)

    STM32使用ADC+DMA进行多通道模拟量采集 (踩坑及通俗解析) ​ 利用STM32的片上外设可采集多个模拟量(如传感器数值),并在嵌入式程序中使用.如果只使用了一个通道,用时令ADC转换而后读取 ...

  2. 爬虫踩坑系列——etree.HTML解析异常

    在爬虫的过程中,难免会遇到各种各样的问题.在这里,为大家分享一个关于etree.HTML解析异常的问题. 1.问题描述: 爬虫过程中,一般会使用requests.get()方法获取一个网页上的HTML ...

  3. Web服务器踩坑之旅03:解析HTTP请求报文

    项目地址: 本文实现的文件在源码中的SimpleWebServer/http_parser目录下 本文内容 目标:解析HTTP报文,从而获取客户请求的文件的文件名及文件地址 浏览器与服务器间的通信过程 ...

  4. Android Kotlin Gson解析踩坑记录

    一.背景 一般我们在进行网络请求拿到返回结果之后,我们期望能够转化成对应的Java实体类,在这个转化过程中,可以使用自动解析的方式,也可以使用三方提供的工具类,比如Gson.FastJson等. 针对 ...

  5. 一次 Scan 竟耗时上百秒?Redis Scan 原理解析与踩坑

    来自:指月 https://www.lixueduan.com 原文:https://www.lixueduan.com/post/redis/redis-scan/ 主要分析了 Redis Scan ...

  6. html5 在线白板,Html5 canvas画图白板踩坑

    最近接手了一个小型的H5,最主要的功能大概就是拍照上传和canvas画板了. 主要是记录一下自己菜到像傻子一样的技术. 1.canvas画板隔空打牛!画布越往上部分错位距离越小,越往下距离越大. 2. ...

  7. android小程序_小程序踩坑记

    小程序踩坑记 希望这个文章能尽量记录下小程序的那些坑,避免开发者们浪费自己的生命来定位到底是自己代码导致的还是啥神秘的字节跳变原因. 前记 小程序大多数坑是同一套代码在不同平台上表现不一致导致的,微信 ...

  8. concat拼接的坑 mysql_DNSlog注入踩坑记录:

    我遇到了两个巨坑,加上http://ceye.io/,经常无法访问,记录一下心酸的踩坑历史. 直接将两个巨坑放到最前面,提醒后人!! 1. sql盲注,后端数据库用的mysql数据库,说一下用dnsl ...

  9. python从入门到实践django看不懂_Python编程:从入门到实践踩坑记 Django

    <>踩坑记 Django Django Python 19.1.1.5 模板new_topic 做完书上的步骤后,对主题添加页面经行测试,但是浏览器显示 服务器异常. 个人采用的开发环境是 ...

  10. 东八区转为0时区_踩坑记 | Flink 天级别窗口中存在的时区问题

    ❝ 本系列每篇文章都是从一些实际的 case 出发,分析一些生产环境中经常会遇到的问题,抛砖引玉,以帮助小伙伴们解决一些实际问题.本文介绍 Flink 时间以及时区问题,分析了在天级别的窗口时会遇到的 ...

最新文章

  1. 近来工作和面试一些人的感受(原)
  2. 视频检测分割--Deep Feature Flow for Video Recognition
  3. torch 和torchvision对应关系并附下载路径
  4. 书籍:Python机器学习蓝图第2版 Python Machine Learning Blueprints 2nd - 2019.pdf
  5. python3 配置文件操作库 configparser 读取配置文件后 元组列表转字典
  6. sklearn 聚类 实例
  7. 评论:Arun Gupta撰写的“ Java EE 6 Pocket Guide”
  8. 痛惜,今年已有多名杰青英年早逝!
  9. 一家化纤工厂的数字化转型之路
  10. 计算机视觉论文-2021-07-16
  11. 计算与推断思维 一、数据科学
  12. mount nfs 经常出错信息总结(转)
  13. high definition audio控制器感叹号_三门峡回收科霸控制器
  14. python调用TensorFlow时报错:FutureWarning: Passing (type, 1) or ‘1type‘ as a synonym of type is deprecated
  15. Android数据库操作-1
  16. ProjectManagement::Redmine中文乱码问题
  17. 我的同学总结关于linux
  18. cookie和session机制之间的区别与联系
  19. 简单快速的视觉里程计入门(Visual odometry)
  20. Keil C51 的printf

热门文章

  1. c语言判断不是大写字母,c语言isupper()函数如何判断字符是否为大写英文字母实例...
  2. 程序员最爱的11个在线社区,你去过几个?
  3. 互联网公司纷纷裁员,大家都在说互联网行业进入了寒冬期,你怎么看待这个说法?
  4. WordPress 速度优化的完整指南
  5. Word在生成PDF后,PDF左侧导航书签没有目录
  6. python设置excel套打_你不一定知道这个用 Python 快速设置 Excel 表格边框的技巧
  7. 最难学的十大编程语言,C++排第二,它竟是第一名!不服
  8. 微信人脸识别-采集个人信息
  9. 08_星仔带你学Java之什么是软件开发以及软件开发方式有哪些?
  10. C# EF The instance of entity type ‘EqInfo‘ cannot be tracked because another instance with the