Place object without overlap others - c#

Im having a problem in one of my projects in unity. I basicly have a system where you click an object and then you click somewhere inside an area and the object goes to that position. the problem is that those objects can't overlap. And they can't use physics. to detect colision with each other. I have been working on this for a long time I got something but still not totally working and I hoped someone could help me.
What im basicly doing is getting all objects near the click and then if there are some just calculate the directions between the click and those objects and then add them to the position that seems to work sometimes they don't overlap other times they do and they go to far away and I need them to be near the click.
code:
public Vector3 GetPossiblePosition(List<GameObject> nearbyDiscs, Vector3 position)
{
float initialY = transform.position.y;
Vector3 idealPosition = position;
List<Vector3> directions = new List<Vector3>();
if(nearbyDiscs.Count > 0)
{
foreach (GameObject disc in nearbyDiscs)
{
Vector3 newDirection = position - disc.transform.position;
directions.Add(newDirection);
}
for (int i = 0; i < directions.Count; i++)
{
idealPosition += directions[i] / directions.Count;
List<GameObject> discs = CheckForNearbyDiscs(idealPosition);
if (discs.Count < 1)
break;
}
}
idealPosition.y = initialY;
return idealPosition;
}
behaviour:

You can easily do this using Physics2D.OverlapCircleAll
public static Collider2D[] OverlapCircleAll(Vector2 point, float radius, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
this method returns an array of colliders, you can simply check overlapping if the length of the returning array is greater than one and can handle accordingly.

Related

The most efficient way to track allowed object movement in Unity 2D for puzzle making

Okay so I've rolled this around in my brain, and while I would probably know what code to use if I could figure it out, I just can't decide what the best way to implement this is.
Basically, say I have a vector grid. This grid could have any number of blockaded areas that the item I am pushing around can't go to. The pushing of the object is done by an interact button using the "new" inputsystem package. I could have a dynamic rigidbody and let it be pushed around that way, but I want to use the interact button I have, in part because the first puzzle of this type is used in a tutorial to teach the player the game's commands.
Without knowing where the blockades are going to be, I'm not sure how to tell where the object can move at any given time. My current thought is use colliders, and keep them far enough away that there isn't an actual collision since I don't think I can place them perfectly enough to get collisions at the right times, and throw out raycasts at a short range to detect these colliders. (This gives me more "give" when placing the colliders.)
Does this sound right? Is there a more efficient way to do it? Even if it requires more complex code, I am interested in feedback because I want to learn "good coding" and how to do things in the best, most efficient way possible, rather than spaghetti coding my way through it.
The most efficient way? Forget Colliders. Basic old-school game dev skill: Working with arrays. Create a 2D array of boolean values, or integers set to 0 or 1 representing OPEN or CLOSED.
using UnityEngine;
public class TestObstacleGrid : MonoBehaviour
{
const int GRID_WIDTH = 8; //if you change this, you'll need to match the size of the grid, below.
int[,] grid = {
// -> +x
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0},
{0,0,0,1,1,1,1,1},
{0,0,0,1,0,1,1,1},
{0,0,0,1,0,1,1,1},
{0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0}
//|
//V
//+z
};
const int OPEN = 0;
const int CLOSED = 1;
public Vector3 playerPos = Vector3.zero; //give player a start position via inspector.
//You need to drag/drop something like a Cube GameObject into here, via the inspector.
public GameObject playerGameObject;
Vector3 playerOffset = new Vector3(0.5f, 0.5f, 0.5f);
Texture2D tex;
void Start()
{
tex = new Texture2D(8, 8, TextureFormat.ARGB32, false);
tex.filterMode = FilterMode.Point; //makes texture crisp, not blurry
//assumes you are using a plane, not a quad.
this.transform.position = new Vector3(GRID_WIDTH / 2, 0, GRID_WIDTH / 2); //shift by half
this.transform.localScale = new Vector3(-GRID_WIDTH / 10f, 1f, -GRID_WIDTH / 10f); //Unity Planes are 10 units wide.
MeshRenderer renderer = this.GetComponent<MeshRenderer>();
renderer.material.mainTexture = tex;
playerGameObject.transform.position = playerPos;
//Only needs to be called once in Start() or Awake(), if grid doesn't change.
RenderGridToTexture();
}
void Update()
{
Vector3 motion = Vector3.zero;
if (Input.GetKeyDown(KeyCode.W))
motion += Vector3.forward;
if (Input.GetKeyDown(KeyCode.S))
motion += Vector3.back;
if (Input.GetKeyDown(KeyCode.A))
motion += Vector3.left;
if (Input.GetKeyDown(KeyCode.D))
motion += Vector3.right;
Vector3 proposedPos = playerPos + motion;
//prevent player leaving the grid.
proposedPos.x = proposedPos.x < 0 ? 0 : proposedPos.x;
proposedPos.x = proposedPos.x > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.x;
proposedPos.z = proposedPos.z < 0 ? 0 : proposedPos.z;
proposedPos.z = proposedPos.z > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.z;
if (grid[(int)proposedPos.z, (int)proposedPos.x] == CLOSED)
{
proposedPos = playerPos; //reset the proposed to the current position (stay there).
Debug.Log("Invalid attempted move from " + playerPos +
" to " + proposedPos);
}
else //OPEN
playerPos = proposedPos;
playerGameObject.transform.position = playerPos + playerOffset; //offset shifts the cube so it looks right.
//Uncomment this if the grid can change during play.
//RenderGridToTexture();
}
void RenderGridToTexture()
{
for (int z = 0; z < GRID_WIDTH; z++)
for (int x = 0; x < GRID_WIDTH; x++)
tex.SetPixel(x, z,
grid[z, x] == 1 ? Color.red : Color.black );
tex.Apply();
}
}
Create an empty GameObject, and attach to this script along with a MeshRenderer and MeshFilter; for the MeshFilter, give it a Plane mesh. Create a Cube GameObject and drag it onto the script's playerGameObject field in the inspector. The script will do the rest.
Using the keys, your player moves one full square at a time, but you'll want smooth movement in the end. That's fine; this is only a basis for your game. Later on, when you want smooth movement, you can figure it out using Mathf.Lerp() with coroutines to move your player smoothly from one space to another (as well as the object your player is pushing).
You'll need to figure out pushing - the principle is the same as already used here to check for collisions: Look at the grid and see if a non-zero value is there. Then look in front of the object to see if there is an obstacle. Use a different number in the grid, like 2, to represent a pushable object.
That's the best I can do in short form, hope it's enough. Adios and good luck.

Unity, how can I make it so a prefab follows a path?

I'm making a small game in Unity and part of it it's that glass balls spawn every few seconds and they follow a path composed of targets, and the prefab has these targets specified:
Prefab picture
And here is the code:
public class move_to_target : MonoBehaviour
{
public GameObject[] target;
public float speed;
int current = 0;
float radius_target = 1;
// Update is called once per frame
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) < radius_target)
{
current = Random.Range(0, target.Length);
if (current >= target.Length)
{
current = 0;
}
}
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
}
}
So, whenever the ball spawns, it should go for the 1st target, then 2nd and finally the 3rd, but when I load the game, all the balls go to the horizon without stopping.
The problem seems to solve itself when I put the ball/fairy prefab in the scene and load the targets in the scene instead of the prefabs but that isn't the ideal solution.
How can I make it so the balls go to the targets in the scene?
Don't have enough rep to comment but this looks like it should work, what do you see if you Debug.Log(target[current].transform.position)?
If you don't need the target game object references, you could store Vector3[]'s instead.
A couple of things: You're randomizing your target every single update frame, so there is actually no progression from one target to the next in any kind of order. Is that intentional?
Also, you're moving the ball transform only when its distance is less than (<) a certain value. It seems more likely that you should be moving the ball as long as its distance is greater than (>) that value. Are you trying to go from a distance of greater than 1, to a distance of less than / equal to one?
If that's the case, maybe something like (untested):
public List<Transform> target = new List<Transform>();
public float speed;
int current = 0;
float radius_target = 1;
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) > radius_target)
{
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
} else {
current++;
if (current == target.Count)
{
current = 0;
}
}
}
But by this method the ball will seek to return to target[0] after target[2] (or whatever is the last one) and keep going forever in a loop. If you want to stop after reaching the last target you'd want to only execute this whole block if the current target < target.Count without returning to zero as it currently does.
And finally, your radius threshold of 1 could either work or not work, depending on how these targets are set up in the scene. This whole approach only works if they are all more than one unit (or radius_target units) away from each other.

how can I show indication from where enemy is coming?

im making a car runner game (all objects are in 3d) where in cops are chasing the player
im spawning the cops from 4 different place (i.e left,right,top,bottom)
private int lastSpawnPos;
[SerializeField]
private Transform[] spawnPos;
void SpawnPoliceCar()
{
GameObject policeCar = ObjectPooling.instance.GetPooledObject("PoliceCar");
int r = UnityEngine.Random.Range(0, spawnPos.Length);
while (lastSpawnPos == r)
{
r = UnityEngine.Random.Range(0, spawnPos.Length);
}
Vector3 policeCarPos = policeCar.transform.position;
policeCarPos = new Vector3(spawnPos[r].position.x, 0, spawnPos[r].position.z);
policeCar.SetActive(true);
policeCar.GetComponent<Damage>().DefaultSetting();
lastSpawnPos = r;
currentPoliceCar++;
}
im calling this method in update() & this script is applied to empty game object in the scene. Since this code works perfectly fine
but now i wanted to add arrow indication on the screen where the cops are spawning as well as i wanted to rotate the arrow as per the direction. im spawning 4 cops at a time.
can any 1 help me in this pls im new to this platform & stuck here from long time
When you're spawning cops you can store them in the List<Transform> cops and remove them (if it's supported to destroy or remove cops from the game). So you'll have player's position and all the cops positions.
Using this data you can find the distance from the cop to the player using Vector3.Distance or Vector2.Distance(if you want to ignore the z coordinate of player and cops). Using this methods you can loop throug cops List and find closets/farthest cop from the player (if you want to show not any cop but closest - the most recently spawned, or closest).
After you have found cop gameobject which you want the arrow to point to you can turn the arrow to this gameobject using Transform.LookAt method. Call this method every Update() call and your arrow's rotation will follow selected cop.
Update:
For the farthest cop your code can look smth like that:
private int lastSpawnPos;
[SerializeField]
private Transform[] spawnPos;
//Your arrow
public GameObject Arrow;
//Player
public Transform Player;
//Storing all spawned cops
public List<Transform> Cops;
void SpawnPoliceCar()
{
GameObject policeCar = ObjectPooling.instance.GetPooledObject("PoliceCar");
int r = UnityEngine.Random.Range(0, spawnPos.Length);
//Adding cop to the list when spawned
//TODO: do not forget to remove from the list, when cop is removed (back to the pool)
Cops.Add(policeCar.transform);
while (lastSpawnPos == r)
{
r = UnityEngine.Random.Range(0, spawnPos.Length);
}
Vector3 policeCarPos = policeCar.transform.position;
policeCarPos = new Vector3(spawnPos[r].position.x, 0, spawnPos[r].position.z);
policeCar.SetActive(true);
policeCar.GetComponent<Damage>().DefaultSetting();
lastSpawnPos = r;
currentPoliceCar++;
}
//Call this on update
void PointArrow()
{
Transform farthestCop = null;
float maxDistance = 0.0f;
//Find the farthes cop
foreach (var cop in Cops)
{
var distance = Vector3.Distance(Player.position, cop.position);
if (distance > maxDistance)
{
farthestCop = cop;
maxDistance = distance;
}
}
//If there are no cops - can't point an arrow
if(farthestCop == null) return;
//Point an arrow on the cop
Arrow.transform.LookAt(farthestCop);
}
I can't exactly say that it is what you are looking for without knowing the full game and code. Hope this will help

Unity2D - Randomly instantiated objects need to not spawn close to each other

I'm a beginner to Unity. I'm creating an educational snake game that randomly spawns three potential answers. It's working, however I need to not have them spawn close to each other. I also need not to have any of them spawn near the player/snake. Someone already tried to help me and I asked him for help, but it seems like he's unable to fix the problem and the objects still spawn near each other.
void GeneratePotentialAnswers()
{
allAnswers.Clear();
List<Vector3> AnswerPositions = new List<Vector3>();
AnswerPositions.Add(player.transform.position);
for (int i = 0; i < potentialAnswers.Count; i++)
{
GameObject ans = Instantiate(enemyAnswerPrefab);
Vector3 pos;
int index = 0;
bool tooClose = false;
do
{
float rndWidth = Random.Range(15f, Screen.width - 25f);
float rndHeight = Random.Range(15f, Screen.height - 105f);
pos = Camera.main.ScreenToWorldPoint(new Vector3(rndWidth, rndHeight, 0f));
pos.z = -1f;
foreach (Vector3 p in AnswerPositions)
{
float distance = (pos - p).sqrMagnitude;
if (distance < 3f)
{
tooClose = true;
}
}
index++;
}
while (tooClose == true && index < 500);
Debug.Log(index);
AnswerPositions.Add(pos);
ans.transform.localPosition = pos;
ans.GetComponent<Answer>().potentialAnswer = potentialAnswers[i];
ans.GetComponent<Answer>().addToScore = scorePerCorrectAnswer;
ans.GetComponent<Answer>().SetAnswer();
allAnswers.Add(ans.GetComponent<Answer>());
}
}
Answer
Manually place GameObjects that act as spawnpoints, attach a SpawnPoint script to each of them.
When placing an answer, have the SpawnPoint script of the chosen spawnpoint use a SphereCastAll to check if the nearby area is free, if it is not, another spawnpoint should be selected until a free one is found.
When a free spawnpoint is found, instantiate the answer at its location.
Elaboration
A simple solution would be to have a SpawnPoint script. Which would be a script put on a set of individual spawnpoints you place manually, there could be perhaps 20 of them at various points in the map.
The SpawnPoint script could then use the SphereCastAll method, which gathers a collection of colliders in a zone of a given size, in the shape of a sphere. (Should work in 3d but maybe not 2d, find another cast type than SphereCastAll in that case).
Here is documentation on how to make a SphereCastAll, notice that you can set the radius of the spherecast.
https://docs.unity3d.com/ScriptReference/Physics.SphereCastAll.html
If any objects are struck by the SphereCastAll, they will be stored in the RaycastHit[] it returns. You can search through this, each RaycastHit will have a reference to the collider it hit, along with the GameObject of that collider. Meaning that you have a list of all objects within a zone.
You can then check these objects to see if they are the player or another answer.
Here is documentation for a RaycastHit.
https://docs.unity3d.com/ScriptReference/RaycastHit.html
Note that to gain access to the spawnpoints it would be useful to have a list of them or a manager script of sorts that can select one for you.

Computationally quickly project 3d object onto 2d plane and get surface area of that (for aerodynamic drag)?

I'm trying to simulate swimming in Unity (using c#) by actually having the movements of the object create drag forces which then propel the object through the liquid.
to do this, I'm using the formula
F = -½ * C * d * velocity squared * A
where C is a coefficient of drag, d is the density of liquid, and A is the object's surface area that faces the direction of motion. A is calculated by projecting the 3D object onto a 2D plane perpendicular to the velocity vector.
Here's an image explaining A:
https://www.real-world-physics-problems.com/images/drag_force_2.png
Now I suspect Unity has a built in way to do this type of projection (since it does that every time there's a camera in the scene).
My question is:
How do I do this? Searches have not helped me with this (unless you're trying to do it with a camera)
Is there a built in function in Unity?
Is this computationally expensive? I am going to be doing this individual for possibly thousands of objects at a time.
I DO NOT need it to be very accurate. I'm just trying to make it a bit realistic, so I want objects with much bigger A to have more drag than ones with much lower A. Slight differences are inconsequential. The objects themselves won't be super complex, but some may have very different areas depending on orientation. So like a cone, for example, could change quite a bit depending on which direction it's moving. I could approximate the A with a simple shape if needed like ellipsoid or rectangle.
If it is computationally expensive, I read a journal article that used a cool way to approximate it. He created a grid of points (which he called voxels) within the objects spaced evenly, which effectively split the object into equal-sized spheres (which always have a cross-sectional surface area of a circle (easy to calculate). Then he calculated the drag force on each of these spheres and added them up to find the total drag (see images).
Images from THESIS REPORT ON: Real-time Physics-based Animation of a
Humanoid Swimmer, Jurgis Pamerneckas, 2014
link https://dspace.library.uu.nl/bitstream/handle/1874/298577/JP-PhysBAnimHumanSwim.pdf?sequence=2
This successfully estimated drag for him. But I see one problem, that the "voxels" that are deep in object are still contributing to drag, where only the ones near the leading edge should be contributing.
So, I thought of a possibility where I could project just the voxel points onto the 2Dplane (perpendicular to velocity) and then find a bounding shape or something, and approximate it that way. I suspect projecting a few points would be faster than projecting a whole 3d object.
this raises a few more questions:
Does this seem like a better method?
How would I create voxels in Unity?
Is it computationally faster?
Any better ideas?
Another thought I had was to do raycasting of some sort, though I can't think of how to do that, perhaps a grid of raycasts parallel to the velocity vector? and just count how many hit to approximate area?
UPDATE
I managed to implement basic drag force by manually typing in the value for A, now I need to approximate A in some way. Even with manual typing, it works surprisingly well for very basic "swimmers". In the image below, the swimmer correctly spins to the right since his left arm is bigger (I gave it double the value for A).
UPDATE 2
Based on #Pierre's comments, I tried computing A for the overall shape using the object's vertices (and also by selecting a few points on the vertices), projecting them onto a plane, and calculating the overall area of the resulting polygon. However, This only calculated the overall drag force on the object. It didn't calculate any rotational drag caused by certain parts of the object moving faster than others. For example, think of a baseball bat swing, the farthest part of the bat will be creating more drag since it's swinging faster than the handle.
This made me go back to the "voxel" idea, since I could calculate local drag sampled at several parts of the object.
I'm playing around with this idea, estimating the voxel's surface area by a circle. But still having a few issues making this estimate relatively accurate. Despite it being inaccurate, this seems to work quite well.
First, I'm using recasts to determine if the voxel can "see" in the direction of the velocity to determine if it's on the leading face of the object. If so, then I take the voxel's local (circular) surface area, and multiplying this by the dot product of the circle's normal and the local velocity vector. This scales the area based on how much it's actually facing the direction of motion.
The inaccuracies so far are due to the circles not actually estimating the local surface area very well, especially for weirdly elongated objects. The further vertices are from each other then the worse the estimation becomes. Any help in this department would be appreciated.
Also, I need to optimize this computationally. Right now, doing it with every vertex is proving to be fairly expensive. I'll keep updating as I progress, and any input would be very helpful! I'll post some code soon once I get a bit farther.
UPDATE 3
I did a fairly accurate implementation using voxels which I manually placed on the surface of the object, and manually estimated the local A when facing that voxel. I then used the dot product to estimate how much of that Area was facing the direction of motion. This worked very well. But the problem then was that even voxels that weren't on the leading edge of the object were contributing to drag. So I used Physics.Raycasts to pop a small distance away from the voxel in the direction of velocity, and then raycast back at the voxel. If this raycast hit the collider of the actual object (not the voxel) it meant it was on the leading edge. This worked fantastically and yielded surprisingly accurate natural looking behaviour of drag. Strangely shaped objects would eventually rotate to minimize drag just like you'd expect. However, as soon as I increased the resolution of voxels and/or added a few more objects into the scene, my frame rate dropped to nearly 3fps. The profiler showed that the brunt of the calculations were due to the raycasting step. I've tried to think of other ways to determine if the voxels are on the leading edge, so far to no avail.
So TLDR, I simulated drag really well, but not in a computationally fast manner.
I never figured out a way to speed up the calculations, but the simulation works great as long as the voxel count is low.
The simulation calculates drag based on the velocity of each voxel. It checks whether it's on the leading edge of the object, and if so applies its drag force.
The code is probably a bit difficult to follow but should at least get you started if you want to try it out. Let me know if you have any questions or need clarifications.
This code is a slightly cleaned up version from my Update#3 above.
In action:
At start of simulation (object moving in straight line towards bottom right of screen)
you can see the force arrows added for visualization and the circles representing the voxels. The force is correctly proportional to the surface area the voxels roughly represent. and only leading edges of the shapes are contributing drag
As the simulation continues, the shape correctly rotates into the most aerodynamic position because of the drag, and the rear sections stop contributing drag.
Drag Enabled Shape Class
this is dragged on main objet (rigidbody) to enable drag. You can either have it create voxels in a spread around a sphere shape. Or load in your own custom Voxels which are game objects with the Voxel Script attached, and are children of this object.
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);
}
}
}
Voxel class
This script is attached to small gameObjects placed around a shape. They represent the locations at which drag is computed. SO for complex shapes these should be at any extremities, and should be fairly spread out over the object. The voxel object's rigid body's mass should approximate the portion of the object this voxel represents.
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);
}
}
VoxelForceVisualizer
This is a attached to prefab of a thin arrow that I put as a child of the voxels to allow force arrows to be drawn during debugging the drag force.
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;
}
}
VoxelVisualizer
this is attached to a small sphere object as a child of the voxel empty. It's just to see where the voxels are, and let the above scripts show/hide the voxels without disabling the drag force calculations.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelVisualizer : MonoBehaviour {
}
DragForce
This calculates the drag force
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);
}
}

Categories

Resources