【质点弹簧实现】Unity 版示例
【质点弹簧实现】Unity 版示例
急速搭建的 Unity 版本的质点弹簧 Demo,不要在意帧率,这个 Demo 没有做任何优化。整个 Demo 就一个文件,直接在 Unity 创建里创建一个名为“MassSpring”的空脚本将下面代码替换进去,然后随便拖到一个场景物体上即可。
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class MassSpring : MonoBehaviour
{
class Spring
{
public readonly Transform pointA;
public readonly Transform pointB;
public float length;
public Spring(Transform pointA, Transform pointB)
{
this.pointA = pointA;
this.pointB = pointB;
}
}
[SerializeField] [Range(0, 2)] float elasticity = 0.75f;
[SerializeField] [Range(0, 0.999f)] float drag = 0.01f;
[SerializeField] int size = 30;
[SerializeField] int unit = 1;
[SerializeField] bool ropeOrCloth;
[SerializeField] bool useGravity;
readonly List<Spring> allSprings = new List<Spring>();
readonly List<Transform> allPoints = new List<Transform>();
readonly Dictionary<Transform, Vector3> lastPositions = new Dictionary<Transform, Vector3>();
void Start()
{
if (ropeOrCloth) //软绳
{
//添加质点
Transform[] points = new Transform[size];
for (int i = 0; i < size; i++)
{
Vector3 position = new Vector3(i * unit, 0, 0);
GameObject point = new GameObject();
allPoints.Add(point.transform);
lastPositions[point.transform] = position;
point.transform.position = position;
points[i] = point.transform;
}
for (int i = 0; i < size - 1; i++)
{
allSprings.Add(new Spring(points[i], points[i + 1]));
}
}
else //布料
{
//添加质点
Transform[,] points = new Transform[size, size];
for (int i = 0, y = 0; i < size; i++, y += unit)
for (int j = 0, x = 0; j < size; j++, x += unit)
{
Vector3 position = new Vector3(x, y, 0);
GameObject point = new GameObject();
allPoints.Add(point.transform);
lastPositions[point.transform] = position;
point.transform.position = position;
points[i, j] = point.transform;
}
//添加弹簧
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
//横纵结构
if (x + 1 < size)
allSprings.Add(new Spring(points[y, x], points[y, x + 1]));
if (y + 1 < size)
allSprings.Add(new Spring(points[y, x], points[y + 1, x]));
//斜向结构
if (x + 1 < size && y + 1 < size) //右下
allSprings.Add(new Spring(points[y, x], points[y + 1, x + 1]));
if (x + 1 < size && y - 1 >= 0) //右上
allSprings.Add(new Spring(points[y, x], points[y - 1, x + 1]));
//抗弯折弹簧
if (x + 2 < size)
allSprings.Add(new Spring(points[y, x], points[y, x + 2]));
if (y + 2 < size)
allSprings.Add(new Spring(points[y, x], points[y + 2, x]));
}
}
foreach (Spring spring in allSprings)
{
spring.length = Vector3.Distance(spring.pointA.position, spring.pointB.position);
}
}
float GetOneMinusDrag(Transform transform)
{
return transform == Selection.activeTransform ? 0 : 1 - drag;
}
void Update()
{
foreach (Transform point in allPoints)
{
//Verlet积分法(但消去了对加速度的计算,完全基于位移控制质点)
//由于直接基于位移修改和计算,后续的每次力计算都会自动考虑力的相互作用,因此弹簧将自带阻力。
//由于直接修改位置实现速度更改,质点积分便不能放在末尾更新,否则会步进两次。
Vector3 position = point.position;
point.position += (position - lastPositions[point]) * GetOneMinusDrag(point);
lastPositions[point] = position;
}
if (useGravity)
foreach (Transform point in allPoints)
{
const float Gravity = 0.02f * 0.02f * -9.81f;
point.position += new Vector3(0, Gravity, 0) * GetOneMinusDrag(point);
}
foreach (Spring spring in allSprings)
{
Transform pointA = spring.pointA;
Transform pointB = spring.pointB;
//使用质点结算了当前速度和力后的位置信息来计算参数(这里力始终直接施加在位移上),这种类似半隐式欧拉的计算方法可以提高模拟稳定性,
//因为它能考虑到与当前其他力的相互作用,从而能实现一定的弹簧阻力,否则得额外实现弹簧阻力。
//但这种阻力也有缺点,它会使质点不再遵循能量守恒(释放的弹力会因其他力变化,而不是基于距离的定值),在单摆实验中无法回到相同的高度,而其他实现阻力的方式没有这种问题。
Vector3 positionA = pointA.position;
Vector3 positionB = pointB.position;
Vector3 vector = positionA - positionB;
// 下面这条注释使用的质点位置是当前帧尚未结算过速度或其他力的位置,也就是说不会受其他力影响,只基于瞬时距离计算,虽然结果不稳定,需要额外配备阻力功能,但它遵循能量守恒定律,可以用单摆做实验
// Vector3 vector = particleA.lastPosition - particleB.lastPosition;
if (vector == Vector3.zero) vector = new Vector3(0, float.Epsilon, 0);
float distance = vector.magnitude;
//胡克定律:弹力=弹力系数*距离*方向
float tendency = distance - spring.length; //距离(可为负数来使方向取反)
Vector3 direction = vector / distance; //方向
//弹力系数实际是有上下限的,若当前质点基于约束位置距离为a,则最高不能超过2a,因为超过后每次迭代,离约束点的位置只会越来越远。
//故此处的弹力系数是基于位移的,范围为[0,2],代表的就是上述的范围限制,因此此处得到的“弹力”实际是位移。
//当弹力系数为1时,得到的位移后顶点是最佳约束点,因为它完美按照了弹簧的距离要求进行位移。
//若非完美约束点,那最远也不能超过最佳约束点1倍距离,因为大于1后每次迭代只会越来越远,最终弹簧将崩溃。
//考虑添加力后将累计速度,而速度也会产生位移,除非有足以抵消的阻力(包括半隐式欧拉积分法导致的阻力),否则弹力系数不能超过1。
Vector3 move = elasticity * tendency * direction;
float moveWeight;
if (ropeOrCloth && drag > 0.99f)
{
//模拟钢绳
moveWeight = 1;
}
else
{
//弹簧会使两端质点一起位移来满足约束要求。要考虑两端质点阻力不同,分配不同的位移权重,
//以便在阻力影响下也能正确达到约束位置,当然最主要的还是为了防止固定点被拉拽。
float oneMinusDragB = GetOneMinusDrag(pointB);
float oneMinusDragA = GetOneMinusDrag(pointA);
moveWeight = Mathf.Approximately(oneMinusDragB + oneMinusDragA, 0.0f) ? 0.5f : oneMinusDragB / (oneMinusDragB + oneMinusDragA);
}
pointB.position += moveWeight * move;
pointA.position += (1 - moveWeight) * -move;
}
}
void OnDrawGizmos()
{
if (allSprings != null)
foreach (Spring spring in allSprings)
{
Gizmos.DrawLine(spring.pointA.position, spring.pointB.position);
}
}
}
【质点弹簧实现】Unity 版示例的更多相关文章
- C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例
C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例 Unity中循环遍历每个数据,并做出判断 很多时候,游戏在玩家做出判断以后,游戏程序会遍历玩家身上大量的所需数据,然后做出判断,即首先判 ...
- 编译opengl编程指南第八版示例代码通过
最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...
- Android/iOS内嵌Unity开发示例
Unity 与 Android/iOS 交叉开发主要有两种方式,以 Android 为例,一是 Android 生成 jar 或者 aar 包,导入到 unity3d plugin/bin/ 目录下: ...
- 正则表达式学习笔记(附:Java版示例代码)
具体学习推荐:正则表达式30分钟入门教程 . 除换行符以外的任意字符\w word,正常字符,可以当做变量名的,字母.数字.下划线.汉字\s space,空白符 ...
- ASP.NET MVC 简介(附VS2019和VSCode版示例)
MVC可以理解为一种思想,应用在web应用程序的架构上. ASP.NET MVC的核心类是实现了IHttpHandler接口的MVCHandler,它的底层仍然是HttpHandler.HttpReq ...
- 安卓集成Unity开发示例(一)
本项目目的是在移动端的 Native App 中以库的形式集成已经写好的 Unity 工程,利用 Unity 游戏引擎便捷的开发手段进行跨平台开发. Unity官方文档 Unity as a Libr ...
- 手写Mybatis和Spring整合简单版示例窥探Spring的强大扩展能力
Spring 扩展点 **本人博客网站 **IT小神 www.itxiaoshen.com 官网地址****:https://spring.io/projects/spring-framework T ...
- Unity Fps示例
https://mp.weixin.qq.com/s/JGnU6TW1V0BCrz0mCRswig
- C++ Primer 第五版示例gcc源码
官方资源,原封不动的.对应于GCC,因此文件名是以此命名的. 门牌号: https://github.com/ZeroPhong/Learning-Resource/blob/master/GCC_4 ...
- Unity 3(一):简介与示例
本文关注以下方面(环境为VS2012..Net Framework 4.5以及Unity 3): Ioc/DI简介: Unity简单示例 一.Ioc/DI简介 IoC 即 Inversion of C ...
随机推荐
- 攻防世界:web习题之disabled_button
攻防世界:web习题之disabled_button 题目内容 https://adworld.xctf.org.cn/challenges/list 打开网页会发现有一个无法点击的按钮 思路 查看该 ...
- 企业IT基础资源管理的“帮帮团”上线啦——源启云原生基础设施管理平台
为助力企业提升基础资源一体化管理和交付效率,以更先进的基础设施管理方式来满足现代企业业务持续扩展和复杂化的需要,中电金信运用基础设施即代码(Infrastructure as Code,简称IaC)技 ...
- vba interpreter 结束
https://github.com/inshua/vba-interpreter 已覆盖几乎 VB 所有的特性,只是库还不够全. VB 语言自身较为落后,语法也有诸多设计不当.最严重的莫过于函数和数 ...
- 【C#】【平时作业】习题-3-数组
1. 设计一个数组用于存放10个整数,然后计算这十个整数之和? private void btn1_Click(object sender, EventArgs e) { int temp = 0; ...
- 在Ubuntu系统上手动安装GCC环境
Ubuntu系统是自带GCC安装指令的apt install gcc,当前apt源中gcc版本为5.4.0,版本太低,推荐手动安装gcc8.3.0 手动安装gcc8.3.0之前需要先确保安装gcc环境 ...
- 关于TB交易开拓者的KDJ指标与经典KDJ指标计算方法不同的分析
我之前在群里咨询的问题, TB官方的技术大佬,给出详细的分析 原链接:https://www.kdocs.cn/l/cgF3sA7ypa99 在这,我只复制最核心的内容,如果需要深入研究, 请联系我, ...
- 【杂谈】Kafka的无锁设计
前言 在分布式消息队列系统中,Kafka 的无锁设计是其高吞吐量和高并发的核心优势之一.通过避免锁的竞争,Kafka 能够在高并发和大规模的生产环境中保持高效的性能.为了更好地理解 Kafka 的无锁 ...
- Qt开发经验小技巧206-210
有时候需要对文本进行分散对齐显示,相当于无论文字多少,尽可能占满整个空间平摊占位宽度,但是在对支持对齐方式的控件比如QLabel调用 setAlignment(Qt::AlignJustify | Q ...
- 微信后团队分享:微信后台基于Ray的分布式AI计算技术实践
本文由微信后台Astra项目团队分享,原题"Ray在微信AI计算中的大规模实践",下文进行了排版和内容优化. 1.引言 微信存在大量AI计算的应用场景,主要分为三种:流量分发.产品 ...
- Swagger UI、RESTful简介
Swagger UI 简介 Swagger UI允许任何人(无论您是开发团队还是最终用户)都可以可视化API资源并与之交互,而无需任何实现逻辑.它是根据您的OpenAPI(以前称为Swagger)规范 ...