【质点弹簧实现】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 ...
随机推荐
- 自定义资源支持:K8s Device Plugin 从原理到实现
本文主要分析 k8s 中的 device-plugin 机制工作原理,并通过实现一个简单的 device-plugin 来加深理解. 1. 背景 默认情况下,k8s 中的 Pod 只能申请 CPU 和 ...
- 07C++选择结构(1)
一.基础知识 1.关系运算符 因为我们要对条件进行判断,必然会用到关系运算符: 名称 大于 大于等于 小于 小于等于 等于 不等于 符号 > >= < <= == != 关系表 ...
- 使用腾讯云对象存储 COS 作为 Velero 后端存储,实现集群资源备份和还原
Velero(以前称为 Heptio Ark)是一个开源工具,可以安全地备份和还原,执行灾难恢复以及迁移 Kubernetes 集群资源和持久卷,可以在 TKE 集群或自建 Kubenetes 集群中 ...
- 中电金信:源启混沌工程平台(V4)与东方通TongwebV7.0完成适配认证
近日,源启混沌工程平台(V4)与北京东方通科技股份有限公司(以下简称东方通)应用服务器软件东方通TongwebV7.0完成产品兼容互认证,通过在产品功能.性能.兼容性方面的全面严格测试,得出结论:东 ...
- git clone 指定 ssh-key 文件
环境 & 软件 mac OS 问题 git clone 不是默认 ssh-key,无法克隆 解决方法 用ssh-add命令将对应的私钥加入到缓存 // ssh-add 自定义名称 // 例子 ...
- Spring Boot中通过RabbitTemplate主动pull(get)消息的例子
import java.util.Properties; import java.util.function.Consumer; import org.slf4j.Logger; import org ...
- [转]Mybatis入门和简单Demo
原文链接: 1.Mybatis入门和简单Demo 2.Mybatis的CRUD案例 3.Mybatis分页查询与动态SQL
- AI应用平台搭建之旅(上) - 框架篇(附:AICon大会阿里国际Agent应用平台分享)
前言 LangEngine内源项目发起于阿里巴巴集团内部组织,LangEngine是类似LLM应用开发框架LangChain的纯Java版本.该框架现已正式对外开源:https://github.co ...
- 2022 年万圣节 Github 彩蛋
记录每年 Github 万圣节彩蛋,也记录有来项目成长历程. 2022 万圣节彩蛋 2021 万圣节彩蛋 2020 万圣节彩蛋
- Note -「Lagrange 反演」记笔习学
也许施工完成啦? 对于常数项为 \(0\),一次项非 \(0\) 的多项式 \(F,G\),定义复合运算 \(\circ\),满足 \[(F\circ G)(x)=F(G(x))=\sum_{ ...