[英]Computationally quickly project 3d object onto 2d plane and get surface area of that (for aerodynamic drag)?
我试图通过实际让物体的运动产生阻力然后推动物体穿过液体来模拟在 Unity 中游泳(使用 c#)。
为此,我正在使用公式
F = -½ * C * d * 速度平方 * A
其中 C 是阻力系数,d 是液体的密度,A 是物体面向运动方向的表面积。 A 是通过将 3D 对象投影到垂直于速度矢量的 2D 平面上来计算的。
这是解释 A 的图像: https : //www.real-world-physics-problems.com/images/drag_force_2.png
现在我怀疑 Unity 有一种内置的方式来进行这种类型的投影(因为每次场景中有摄像机时它都会这样做)。
我的问题是:
Unity 中是否有内置函数?
这在计算上是昂贵的吗? 我将一次为可能数以千计的对象做这个人。
我不需要它非常准确。 我只是想让它有点现实,所以我希望 A 大得多的物体比 A 小得多的物体具有更大的阻力。细微的差异是无关紧要的。 对象本身不会非常复杂,但根据方向的不同,有些对象可能具有非常不同的区域。 例如,就像一个圆锥体,根据它移动的方向,它可能会发生很大的变化。 如果需要,我可以用一个简单的形状来近似 A,比如椭圆体或矩形。
如果它的计算成本很高,我会阅读一篇期刊文章,其中使用了一种很酷的方法来近似它。 他在均匀分布的物体内创建了一个点网格(他称之为体素),这有效地将物体分成了大小相等的球体(这些球体的横截面面积始终为圆形(易于计算)。然后他计算了每个球体上的阻力并将它们相加以找到总阻力(见图)。
论文报告中的图像:基于物理的实时人形游泳者动画,Jurgis Pamerneckas,2014
链接https://dspace.library.uu.nl/bitstream/handle/1874/298577/JP-PhysBAnimHumanSwim.pdf?sequence=2
这成功地估计了他的阻力。 但我看到了一个问题,即物体深处的“体素”仍然对阻力有贡献,只有靠近前缘的那些才应该有贡献。
所以,我想到了一种可能性,我可以只将体素点投影到 2D 平面上(垂直于速度),然后找到一个边界形状或其他东西,并以这种方式近似。 我怀疑投影几个点会比投影整个 3d 对象更快。
这又引发了几个问题:
我的另一个想法是进行某种光线投射,尽管我想不出如何做到这一点,也许是平行于速度矢量的光线投射网格? 并计算一下近似区域的命中次数?
更新
我设法通过手动输入 A 的值来实现基本的阻力,现在我需要以某种方式近似 A。 即使使用手动打字,它对于非常基本的“游泳者”也非常有效。 在下图中,游泳者正确地向右旋转,因为他的左臂更大(我给了它两倍的 A 值)。
更新 2
根据@Pierre 的评论,我尝试使用对象的顶点(以及通过选择顶点上的几个点)计算整体形状的 A,将它们投影到平面上,并计算生成的多边形的总面积。 然而,这只计算了物体上的整体阻力。 它没有计算由物体的某些部分比其他部分移动得更快引起的任何旋转阻力。 例如,想想棒球棒的挥杆,球棒最远的部分会产生更大的阻力,因为它比手柄挥动得更快。
这让我回到了“体素”的想法,因为我可以计算在对象的几个部分采样的局部阻力。
我正在考虑这个想法,通过一个圆圈来估计体素的表面积。 但是仍然存在一些问题,使这个估计相对准确。 尽管它不准确,但这似乎工作得很好。
首先,我使用重铸来确定体素是否可以在速度方向上“看到”以确定它是否在对象的引导面上。 如果是这样,那么我取体素的局部(圆形)表面积,并将其乘以圆的法线和局部速度矢量的点积。 这会根据实际面向运动方向的程度来缩放区域。
到目前为止的不准确是由于圆圈实际上没有很好地估计局部表面积,特别是对于奇怪的细长物体。 顶点彼此相距越远,估计就越差。 本部门的任何帮助将不胜感激。
另外,我需要在计算上优化它。 现在,事实证明对每个顶点都这样做是相当昂贵的。 随着我的进步,我会不断更新,任何输入都会非常有帮助! 一旦我走得更远,我会很快发布一些代码。
更新 3
我使用手动放置在对象表面上的体素做了一个相当准确的实现,并在面对该体素时手动估计了局部 A。 然后我使用点积来估计该区域有多少面向运动方向。 这非常有效。 但问题是,即使不在物体前沿的体素也会导致阻力。 所以我使用 Physics.Raycasts 在速度方向上从体素弹出一小段距离,然后在体素上进行光线投射。 如果此光线投射击中实际物体(而不是体素)的对撞机,则意味着它位于前缘。 这非常有效,并产生了令人惊讶的准确自然的阻力行为。 形状奇怪的物体最终会像您期望的那样旋转以最大限度地减少阻力。 然而,一旦我增加了体素的分辨率和/或在场景中添加了更多的对象,我的帧速率就会下降到接近 3fps。 分析器显示,计算的首当其冲是由于光线投射步骤。 我试图想出其他方法来确定体素是否处于前沿,但目前无济于事。
所以 TLDR,我很好地模拟了阻力,但不是以快速计算的方式。
我从来没有想出加速计算的方法,但只要体素数很低,模拟就可以很好地工作。
模拟根据每个体素的速度计算阻力。 它检查它是否在对象的前缘,如果是,则应用其阻力。
该代码可能有点难以理解,但如果您想尝试一下,至少应该让您入门。 如果您有任何问题或需要澄清,请告诉我。
这段代码是我上面更新#3 的一个稍微清理过的版本。
实际操作:模拟开始时(对象沿直线向屏幕右下方移动)
您可以看到为可视化添加的力箭头和代表体素的圆圈。 力与体素粗略表示的表面积成正比。 并且只有形状的前缘会产生阻力
随着模拟的继续,由于阻力,形状正确旋转到最符合空气动力学的位置,后部停止提供阻力。
拖动启用形状类
这是在主对象(刚体)上拖动以启用拖动。 您可以让它围绕球体形状创建体素。 或者加载您自己的自定义体素,这些体素是附加了体素脚本的游戏对象,并且是该对象的子代。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
[RequireComponent (typeof(Rigidbody))]
public class DragEnabledShape : MonoBehaviour {
const float EPSILON = 0.0001f;
public Voxel voxelPrefab;
public float C = 1f;
public float d = 0.5f;
public int resolutionFactor = 2;
public float maxDistanceFromCenter = 10f;
public bool displayDragVisualization = false;
public float forceVisualizationMultiplier = 1f;
public bool displayVoxels = false;
public bool loadCustomVoxels = false;
List<Voxel> voxels;
Rigidbody rb;
// Use this for initialization
void Awake () {
voxels = new List<Voxel> ();
rb = GetComponent<Rigidbody> ();
}
void OnEnable () {
if (loadCustomVoxels) {
var customVoxels = GetComponentsInChildren<Voxel> ();
voxels.AddRange (customVoxels);
if (displayDragVisualization) {
foreach (Voxel voxel in customVoxels) {
voxel.DisplayDrag (forceVisualizationMultiplier);
}
}
if (displayVoxels) {
foreach (Voxel voxel in customVoxels) {
voxel.Display ();
}
}
}
else {
foreach (Transform child in GetComponentsInChildren<Transform> ()) {
if (child.GetComponent<Collider> ()) {
//print ("creating voxels of " + child.gameObject.name);
CreateSurfaceVoxels (child);
}
}
}
}
void CreateSurfaceVoxels (Transform body) {
List<Vector3> directionList = new List<Vector3> ();
for (float i = -1; i <= 1 + EPSILON; i += 2f / resolutionFactor) {
for (float j = -1; j <= 1 + EPSILON; j += 2f / resolutionFactor) {
for (float k = -1; k <= 1 + EPSILON; k += 2f / resolutionFactor) {
Vector3 v = new Vector3 (i, j, k);
directionList.Add (v);
}
}
}
//float runningTotalVoxelArea = 0;
foreach (Vector3 direction in directionList) {
Ray upRay = new Ray (body.position, direction).Reverse (maxDistanceFromCenter);
RaycastHit[] hits = Physics.RaycastAll (upRay, maxDistanceFromCenter);
if (hits.Length > 0) {
//print ("Aiming for " + body.gameObject.name + "and hit count: " + hits.Length);
foreach (RaycastHit hit in hits) {
if (hit.collider == body.GetComponent<Collider> ()) {
//if (GetComponentsInParent<Transform> ().Contains (hit.transform)) {
//print ("hit " + body.gameObject.name);
GameObject empty = new GameObject ();
empty.name = "Voxels";
empty.transform.parent = body;
empty.transform.localPosition = Vector3.zero;
GameObject newVoxelObject = Instantiate (voxelPrefab.gameObject, empty.transform);
Voxel newVoxel = newVoxelObject.GetComponent<Voxel> ();
voxels.Add (newVoxel);
newVoxel.transform.position = hit.point;
newVoxel.transform.rotation = Quaternion.LookRotation (hit.normal);
newVoxel.DetermineTotalSurfaceArea (hit.distance - maxDistanceFromCenter, resolutionFactor);
newVoxel.attachedToCollider = body.GetComponent<Collider> ();
if (displayDragVisualization) {
newVoxel.DisplayDrag (forceVisualizationMultiplier);
}
if (displayVoxels) {
newVoxel.Display ();
}
//runningTotalVoxelArea += vox.TotalSurfaceArea;
//newVoxel.GetComponent<FixedJoint> ().connectedBody = shape.GetComponent<Rigidbody> ();
}
else {
//print ("missed " + body.gameObject.name + "but hit " + hit.transform.gameObject.name);
}
}
}
}
}
void FixedUpdate () {
foreach (Voxel voxel in voxels) {
rb.AddForceAtPosition (voxel.GetDrag (), voxel.transform.position);
}
}
}
体素类
此脚本附加到围绕形状放置的小游戏对象。 它们代表计算阻力的位置。 所以对于复杂的形状,这些应该在任何四肢,并且应该在物体上相当分散。 体素对象的刚体质量应近似于该体素所代表的对象部分。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Voxel : MonoBehaviour {
Vector3 velocity;
public Collider attachedToCollider;
Vector3 drag;
public Vector3 Drag {
get {
return drag;
}
}
float dragMagnitude;
public float DragMagnitude {
get {
return dragMagnitude;
}
}
bool leadingEdge;
public bool LeadingEdge {
get {
return leadingEdge;
}
}
bool firstUpdate = true;
public float localSurfaceArea;
Vector3 prevPos;
public VoxelForceVisualizer forceVisualizer;
public VoxelVisualizer voxelVisualizer;
const float AREA_COEFFICIENT = 1.1f;
const float EPSILON = 0.001f;
const float FAR_DISTANCE = 5f;
const float MAX_FORCE = 100f;
public void DetermineTotalSurfaceArea (float distanceFromCenter, float resolution) {
float theta = (Mathf.PI / 4) / resolution;
float localR = distanceFromCenter * Mathf.Tan (theta) * AREA_COEFFICIENT;// * (resolution / 0.01f);
localSurfaceArea = Mathf.PI * localR * localR;
}
bool IsVisibleFromPlane () {
if (attachedToCollider == null) {
throw new MissingReferenceException ("attached to collider not set");
}
bool visibleFromPlane = false;
//checks if this is leading edge of this part of object.
Ray justOutsideSurface = new Ray (this.transform.position, velocity).Reverse (EPSILON);
RaycastHit hit;
if (Physics.Raycast (justOutsideSurface, out hit, EPSILON * 2f)) {
if (hit.collider == attachedToCollider) {
//checks if other parts of this object are in front, blocking airflow.
//Ray wayOutsideSurface = new Ray (this.transform.position, velocity).Reverse (FAR_DISTANCE);
//RaycastHit firstHit;
//if (Physics.Raycast (wayOutsideSurface, out firstHit, FAR_DISTANCE * 2f)) {
//if (firstHit.collider == attachedToCollider) {
visibleFromPlane = true;
//}
//}
}
}
//}
leadingEdge = visibleFromPlane;
return visibleFromPlane;
}
void FixedUpdate () {
if (firstUpdate) {
prevPos = transform.position;
firstUpdate = false;
}
velocity = (transform.position - prevPos) / Time.deltaTime;
prevPos = transform.position;
}
public Vector3 GetDrag () {
if (IsVisibleFromPlane ()) {
float alignment = Vector3.Dot (velocity, this.transform.forward);
float A = alignment * localSurfaceArea;
dragMagnitude = DragForce.Calculate (velocity.sqrMagnitude, A);
//This clamp is necessary for imperfections in velocity calculation, especially with joint limits!
//dragMagnitude = Mathf.Clamp (dragMagnitude, 0f, MAX_FORCE);
drag = -velocity * dragMagnitude;
}
return drag;
}
public void Display () {
voxelVisualizer.gameObject.SetActive (true);
}
public void TurnOffDisplay () {
voxelVisualizer.gameObject.SetActive (false);
}
public void DisplayDrag (float forceMultiplier) {
forceVisualizer.gameObject.SetActive (true);
forceVisualizer.multiplier = forceMultiplier;
}
public void TurnOffDragDisplay () {
forceVisualizer.gameObject.SetActive (false);
}
}
体素力可视化器
这是附加到细箭头的预制件,我将其作为体素的子项放置,以允许在调试阻力期间绘制力箭头。
using UnityEngine;
public class VoxelForceVisualizer : MonoBehaviour {
const float TINY_NUMBER = 0.00000001f;
public Voxel voxel;
public float drag;
public float multiplier;
void Start () {
voxel = this.GetComponentInParent<Voxel> ();
}
// Update is called once per frame
void Update () {
Vector3 rescale;
if (voxel.LeadingEdge && voxel.Drag != Vector3.zero) {
this.transform.rotation = Quaternion.LookRotation (voxel.Drag);
rescale = new Vector3 (1f, 1f, voxel.DragMagnitude * multiplier);
}
else {
rescale = Vector3.zero;
}
this.transform.localScale = rescale;
drag = voxel.DragMagnitude;
}
}
体素可视化器
这作为体素空的子级附加到一个小球体对象上。 这只是为了查看体素的位置,并让上述脚本显示/隐藏体素而不禁用阻力计算。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelVisualizer : MonoBehaviour {
}
拖拽力
这将计算阻力
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class DragForce {
const float EPSILON = 0.000001f;
public static float Calculate (float coefficient, float density, float vsq, float A) {
float f = coefficient * density * vsq * A;
return f;
}
public static float Calculate (float vsq, float A) {
return Calculate (1f, 1f, vsq, A);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.