我从来没有想出加快计算速度的方法,但只要体素数量少,模拟效果就很好。
模拟根据每个体素的速度计算阻力。它检查它是否在对象的前缘,如果是,则应用它的阻力。
代码可能有点难以理解,但如果您想尝试一下,至少应该可以帮助您入门。如果您有任何问题或需要澄清,请告诉我。
此代码是我上面的 Update#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);
}
}