【质点弹簧实现】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 版示例的更多相关文章

  1. C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例

    C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例 Unity中循环遍历每个数据,并做出判断 很多时候,游戏在玩家做出判断以后,游戏程序会遍历玩家身上大量的所需数据,然后做出判断,即首先判 ...

  2. 编译opengl编程指南第八版示例代码通过

    最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...

  3. Android/iOS内嵌Unity开发示例

    Unity 与 Android/iOS 交叉开发主要有两种方式,以 Android 为例,一是 Android 生成 jar 或者 aar 包,导入到 unity3d plugin/bin/ 目录下: ...

  4. 正则表达式学习笔记(附:Java版示例代码)

    具体学习推荐:正则表达式30分钟入门教程 .         除换行符以外的任意字符\w      word,正常字符,可以当做变量名的,字母.数字.下划线.汉字\s        space,空白符 ...

  5. ASP.NET MVC 简介(附VS2019和VSCode版示例)

    MVC可以理解为一种思想,应用在web应用程序的架构上. ASP.NET MVC的核心类是实现了IHttpHandler接口的MVCHandler,它的底层仍然是HttpHandler.HttpReq ...

  6. 安卓集成Unity开发示例(一)

    本项目目的是在移动端的 Native App 中以库的形式集成已经写好的 Unity 工程,利用 Unity 游戏引擎便捷的开发手段进行跨平台开发. Unity官方文档 Unity as a Libr ...

  7. 手写Mybatis和Spring整合简单版示例窥探Spring的强大扩展能力

    Spring 扩展点 **本人博客网站 **IT小神 www.itxiaoshen.com 官网地址****:https://spring.io/projects/spring-framework T ...

  8. Unity Fps示例

    https://mp.weixin.qq.com/s/JGnU6TW1V0BCrz0mCRswig

  9. C++ Primer 第五版示例gcc源码

    官方资源,原封不动的.对应于GCC,因此文件名是以此命名的. 门牌号: https://github.com/ZeroPhong/Learning-Resource/blob/master/GCC_4 ...

  10. Unity 3(一):简介与示例

    本文关注以下方面(环境为VS2012..Net Framework 4.5以及Unity 3): Ioc/DI简介: Unity简单示例 一.Ioc/DI简介 IoC 即 Inversion of C ...

随机推荐

  1. 编译器-FOLLOW集合

    语法分析器的两个重要函数 FIRST和FOLLOW 一.FOLLOW的定义 在句型中紧跟在A右边的终结符号的集合 如果A是某些句型的最右符号,那么$在FOLLOW(A)中 A:非终结符 二.计算方法 ...

  2. 【Android】谷歌应用关机闹钟 PowerOffAlarm 源码分析,并实现定时开、关机

    前言 RTC RTC 即实时时钟(Real-Time Clock),主要是功能有: 时间保持:RTC可以在断电的时候,仍然保持计时功能,保证时间的连续性 时间显示与设置:RTC可以向系统提供年.月.日 ...

  3. C#/.NET/.NET Core技术前沿周刊 | 第 17 期(2024年12.09-12.15)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...

  4. 【Javaweb】在项目中添加MyBatis依赖等

    pom.xml 仓库 如果你没有配置阿里云仓库镜像源,可以到这里来找 https://mvnrepository.com/ 如果你配置了阿里云仓库镜像源,可以来这里找 https://develope ...

  5. Go语言实现国密证书加密与解析技术详解

    Go语言实现国密证书加密与解析技术详解 前言 在当今数字化时代,信息安全成为企业和个人关注的焦点.国密算法作为中国自主研发的加密标准,广泛应用于各类安全场景.Go语言以其简洁.高效的特性,成为众多开发 ...

  6. trim-all-strings-elements-in-a-complex-object

    package com.xxx.common.util; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.Strin ...

  7. 创建没有构造函数的NumberFormat

    我有以下课程: import java.text.NumberFormat; public static class NF { public static NumberFormat formatSha ...

  8. git学习之git reset命令

    Git版本恢复命令 reset命令有3种方式: git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和inde ...

  9. Qt音视频开发39-人脸识别在线版

    一.前言 关于人脸识别这块,前些年不要太火,哪怕是到了今天依然火的一塌糊涂,什么玩意都要跟人脸识别搭个边,这东西应该只是人工智能的一个很小的部分,人脸识别光从字面上理解就是识别出人脸区域,其实背后真正 ...

  10. vue:引入外部cdn报错 ‘XXX is not defined’ 及事件处理办法

    框架:vue-cli(vue脚手架) 例:以cdn引入腾讯防水墙为例 前因:在html的head中引入外部cdn链接, 在vue文件中直接使用,如图 结果:如图报错 解决办法: 1. 在index.h ...