【质点弹簧实现】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 ...
随机推荐
- openEuler欧拉配置Nacos集群
一.安装Nacos systemctl stop firewalld systemctl disable firewalld mkdir -p /home/nacos tar xvf nacos- ...
- Centos查找Tomcat路径并重启
[root@devrestcloud ~\]# find / -name \*tomcat\* [root@devrestcloud ~]# cd /usr/tomcat/apache-tomcat- ...
- vue3笔记 - 父子组件通信
父传子 说明:父组件将数据绑定在组件标签上:子组件props接收 父组件: <template> <Child :msg="msg" /> </tem ...
- Flutter null safety 无法运行
Flutter空安全问题 在pub上有一些库导入之后无法运行,这是因为健全的空安全 解决方法 1.在命令行中添加参数 flutter run --no-sound-null-safety 2.在IDE ...
- Qt开发经验小技巧181-185
Qt天生就是linux的,从linux开始发展起来的,所以不少Qt程序员经常的开发环境是linux,比如常用的ubuntu等系统,整理了一点常用的linux命令. 命令 功能 sudo -s 切换到管 ...
- OpenWrt安装腾讯云DDNS插件
1.插件介绍 OpenWRT TencentDDNS插件是一款腾讯云研发的,自动映射动态公网IP至用户指定的DNSPod域名解析记录的官方插件. 标题 名称 中文名称 腾讯云DDNS插件 英文名称 l ...
- OGC——WMS服务
一.引言 一直用arcgis javascript的二次开发,经常使用它的一些服务WMS.WMTS.WFS.MapService等,并没有深入了解这些服务内部机制,直到最近学习了geoserver,由 ...
- Android 系统使RNDIS网卡上网
背景说明: 一位台湾客户需要采购一批SIMCOM SIM6600CE模组用于Tinker board2s,需要适配Debain系统和Android系统. 主要修改点: 1.defconfig 增加RN ...
- 【转载】hacker术语
1.肉鸡 所谓"肉鸡"是一种很形象的比喻,比喻那些可以随意被我们控制的电脑,对方可以是WINDOWS系统,也可以是UNIX/LINUX系统,可以是普通的个人电脑,也可以是大型的服务 ...
- ATM 管理系统的设计与实现(类似毕业设计,附源代码)
ATM 管理系统的设计与实现 作者前言:本系统通过基本规范化的设计,简单的利用了java基本功能实现了ATM系统,本系统虽然简单,但是逻辑很严密,对于有一定java知识的读者有较大帮助,可以用作参考. ...