物理模拟
可能… 是我最开始学编程,做小玩具的时候喜欢的想做的一些东西,今天想整理一下,顺便学一下怎么写数学公式,调调博客各种内容的样式。
Web 动画
Web 画图可以用一个个小元素用 dom svg 拼,也可以用 canvas,但像物理模拟这种事情还是用 canvas 比较合适。
而,除了画图,让画面动起来,物理模拟也还需要有时间的概念。
这在 web 中有许多方式,古老的 setInterval 会按照传入的时间间隔去调用一个函数,这个函数自然可以是更新画面。
而对于动画,专门的 requestAnimationFrame 会更合适,会在浏览器每次重绘之前调用传入的函数,传入的回调也会给一个时间戳的入参,外面写个变量记一下也能挺容易算出两次调用的时间间隔 。而对回调函数的调用频率也会和设备刷新率相匹配,也许通常是 60Hz。总之,对于动画,用它就不用再考虑很多奇奇怪怪的问题了。
let lastfunction draw(now) { const diff = time - last // delta time last = now /* 更新画面 ...... */ requestAnimationFrame(draw);}时间积分/时间离散/时间步进
上面提到的 web 中有关时间和动画 的 API 都是离散的,所以,一些物理模拟中的时间也一样要跟着是离散的。
对于一般的运动模拟,通常关注 x, v, a 这几个物理量就足够了。
怎么做呢?
最简单的
这应该算最简单的方法了,简单到我甚至才知道它还有个专门名字,叫欧拉方法,知道它们有好多种,统称数值方法。
Verlet 积分
最近又看到一个 Verlet 积分:
还有带速度版本的:
看起来它在计算位移时不仅考虑了 v 还同时考虑了 a,像泰勒展开多展开了几项,应该会更精确一些吧…
去查了查那些别的数值方法,这些东西长的都像是能用泰勒展开推出来的,一些更简单方便计算的式子…?
虽然我会觉得怀疑它们一开始是被用各种稀奇古怪的手段弄出来的。
弹簧与碰撞
胡克定律
然而,在实际应用中,许多材料的弹性行为是非线性的。当应力超过一定阈值时,材料的应变与应力之间的关系将不再保持线性。为了描述这种非线性行为,需要对胡克定律进行扩展。一种常见的非线性扩展形式是:
物理中的碰撞
弹性碰撞时的动量,能量守恒
推导碰撞后的速度
最后推导的结果其实是,某种对称的,交换12位置就是互相的式子。
所以,对于1和2如果将当前想要求解速度的物体记为self,另一个物体记为other,最后的这两个式子其实是一个
游戏中的碰撞
然而,在游戏中,质量并不是一个常被关注量,游戏中更需要花功夫考虑的是不是碰撞结果的计算而是碰撞的检测。
// AABB碰撞检测function aabbCollision(rect1, rect2) { return rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y;}
// 圆形碰撞检测function circleCollision(circ1, circ2) { const dx = circ1.cx - circ2.cx; const dy = circ1.cy - circ2.cy; const distance = Math.sqrt(dx * dx + dy * dy); return distance < circ1.radius + circ2.radius;}
// 分离轴定理(SAT)检测多边形碰撞function satCollision(poly1, poly2) { // 获取多边形的边向量 function getEdges(points) { const edges = []; for (let i = 0; i < points.length; i++) { const p1 = points[i]; const p2 = points[(i + 1) % points.length]; edges.push({ x: p2.x - p1.x, y: p2.y - p1.y }); } return edges; }
// 获取垂直于边的轴 function getAxes(edges) { return edges.map(edge => ({ x: -edge.y, y: edge.x })); }
// 投影多边形到轴上 function project(points, axis) { let min = points[0].x * axis.x + points[0].y * axis.y; let max = min;
for (const p of points) { const val = p.x * axis.x + p.y * axis.y; min = Math.min(min, val); max = Math.max(max, val); } return { min, max }; }
// 检查投影是否重叠 function projectionsOverlap(p1, p2) { return !(p1.max < p2.min || p2.max < p1.min); }
const edges1 = getEdges(poly1.points); const edges2 = getEdges(poly2.points); const axes1 = getAxes(edges1); const axes2 = getAxes(edges2);
// 检查所有轴 for (const axis of [...axes1, ...axes2]) { const p1 = project(poly1.points, axis); const p2 = project(poly2.points, axis);
if (!projectionsOverlap(p1, p2)) { return false; // 找到分离轴,无碰撞 } }
return true; // 所有轴都重叠,有碰撞}以及,处理大量物体时,会考虑做空间分割,只计算一个区域内的物体是否有碰撞。
弹簧质点模型
最后,我想做那种软软弹弹的像果冻一样的效果
弹簧质点模型,就像它的名字一样,对于一个物体,不是一下创完整体,而摆好一堆质点,将它们用弹簧连起来,然后你就得到了一坨软软的东西。
而对弹性,对质点间弹簧的不同处理则会,有不一样的软软的感觉。
对于布料,只将质点间十字连接就好,对于弹簧的性质,会需要那种比较难拉伸但比较容易压缩弯曲的弹簧。
而对于果冻,为了产生形变同时又防止过分的折叠和压缩,会考虑十字加上对角线方向的弹簧,会需要中等的拉伸与压缩系数。
// Create jelly particlesconst startX = width / 4;const startY = height / 2;
for (let y = 0; y < validGridSize; y++) { for (let x = 0; x < validGridSize; x++) { const px = startX + x * particleRadius * 1.5 const py = startY + y * particleRadius * 1.5
// No fixed particles for better movement this.particles.push(new Particle(px, py, false)); }}
// Create springs between adjacent particlesfor (let y = 0; y < validGridSize; y++) { for (let x = 0; x < validGridSize; x++) { const index = y * validGridSize + x; if (!this.particles[index]) continue;
// Connect to right neighbor if (x < validGridSize - 1) { const rightIndex = y * validGridSize + (x + 1); if (this.particles[rightIndex]) { this.springs.push(new Spring( this.particles[index], this.particles[rightIndex], null, this.params.stiffness, this.params.nonLinearity )); } }
// Connect to bottom neighbor if (y < validGridSize - 1) { const bottomIndex = (y + 1) * validGridSize + x; if (this.particles[bottomIndex]) { this.springs.push(new Spring( this.particles[index], this.particles[bottomIndex], null, this.params.stiffness, this.params.nonLinearity )); } }
// Connect to diagonal neighbors if (x < validGridSize - 1 && y < validGridSize - 1) { const diagIndex = (y + 1) * validGridSize + (x + 1); if (this.particles[diagIndex]) { this.springs.push(new Spring( this.particles[index], this.particles[diagIndex], null, this.params.stiffness * 0.7, this.params.nonLinearity )); } }
if (x < validGridSize - 1 && y > 0) { const diagIndex = (y - 1) * validGridSize + (x + 1); if (this.particles[diagIndex]) { this.springs.push(new Spring( this.particles[index], this.particles[diagIndex], null, this.params.stiffness * 0.7, this.params.nonLinearity )); } } }}最后的最后
关于果冻,在 2D 游戏开发中,果冻效果的一个重要特征是面积守恒。
果冻效果的参数调节是实现真实感的关键。主要的参数包括:弹性系数,控制材料的硬度;粘性系数,控制材料的粘稠程度;表面张力系数,影响表面的收缩行为;密度,控制材料的质量分布;阻尼系数,控制振动的衰减速度。这些… 就不想再做了
关于物理数值模拟的计算,还有种叫做 pbd 的操作,基于位置与约束条件去做计算,而再不去做什么受力分析。听说和拉格朗日力学,最小作用量之类的有关… 看不懂
关于弹簧质点模型,我觉得它也许会像有限元分析小时候,那时候它还很可爱。但有限元听起来还是有点哈人了也不想看了…
↓,调小 Stiffness 前要把 Gravity 归零,不然会被压塌x