Unity rigid object receives twice the force it is supposed to receive - c#

I am making a game where bullets can start orbiting a player. The orbits are circular, and not elliptical. Bullets have a drag factor of 0.
At first, I simply calculated the next position in the orbit, and divided the delta position by fixedDeltaTime to obtain its new velocity.
Unfortunately, it meant that fast bullets instead follow polygonal paths, and often miss a target.
I wanted to improve the precision by giving them an inward force, and a velocity nearly tangential to the orbit, so that they follow parabola segments around the player instead of straight lines.
The parabola segments are defined by their start and end point, and their vertices, which must lay on the orbit arc, with a velocity equal to the velocity I was previous using ((endpos - startpos) / fixedDeltaTime).
To calculate the parabola, I calculate the midway points on the arc and segment, and their difference is proportional to the force applied.
so we use the following names
fdt = fixedDeltaTime
t = fdt / 2
f = the force applied during the incoming physics frame
v0 = the velocity at the start of the frame
v1 = the velocity at the middle of the frame (at the vertex of the parabola)
dp1 = the relative position at the middle of the frame (midpos - startpos) and vertex of the parabola
dp2 = the relative position at the end of the frame (endpos - startpos)
The force is defined by these two equations:
// at vertex, only the "lateral" velocity remains
v1 = dp2 / fdt
// the difference between dp2 / 2 and dp1 is what the force can apply over half a frame
dp2 / 2 - dp1 = f * 0.5tt
therefore
(dp2 / 2 - dp1) / (0.5 * t * t) = f
(dp2 / 2 - dp1) / (0.5 * fdt/2 * fdt/2) = f
(dp2 - dp1 * 2) * 4 / (fdt * fdt) = f
//v0 is then easily calculated
v0 = v1 - t * force
v0 = (dp2 / fdt) - force * (fdt / 2)
We then get this working code:
Vector3 startPos = _rigidbody.position;
if (firstFrame == false && Vector3.Distance(predictedNextFramePos, startPos) > 0.01f)
Debug.Log("WTF");
Vector3 nextPosition;
GetLocalOrbitPos(nextTime, out nextPosition);
nextPosition += _parent.GetPosition();
float fdt = Time.fixedDeltaTime;
float halfTime = (time + nextTime) / 2f;
Vector3 halfPosition;
GetLocalOrbitPos(halfTime, out halfPosition);
halfPosition += _parent.GetPosition();
Vector3 dp2 = nextPosition - startPos;
Vector3 dp1 = halfPosition - startPos;
Vector3 force = (dp2 - 2 * dp1) * 4 / (fdt * fdt);
Vector3 v0 = (dp2 / fdt) - (force * fdt / 2f);
Vector3 deltaPosPredicted = PhysicsTools.GetMovement(v0, force, fdt);
if (Vector3.Distance(deltaPosPredicted, dp2) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaPosPredicted, dp2));
predictedNextFramePos = deltaPosPredicted + startPos;
Vector3 deltaHPosPredicted = PhysicsTools.GetMovement(v0, force, fdt / 2f);
if (Vector3.Distance(deltaHPosPredicted, dp1) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaHPosPredicted, dp1));
//drawing the startpos, midpos, endpos triangle
Debug.DrawLine(startPos, startPos + dp2, Color.magenta, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos + dp2, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
//drawing v0 and force
Debug.DrawLine(startPos, startPos + force, Color.gray, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + v0, Color.blue, Time.fixedDeltaTime * 2);
//drawing the parabola arc
{
Vector3 pos = startPos;
Vector3 vel = v0;
for (int i = 0; i < 10; i++)
{
Vector3 offset = PhysicsTools.GetMovementUpdateVelocity(ref vel, force, Time.fixedDeltaTime / 10f);
Debug.DrawLine(pos, pos + offset, Color.green, Time.fixedDeltaTime * 2);
pos += offset;
}
}
// Old version
// Vector3 deltaPosition = nextPosition - _rigidbody.position;
// Vector3 velocity = deltaPosition / t;
// SetPhysicsState(_rigidbody.position, velocity, time);
//Applying results
SetPhysicsState(startPos, v0, time);
_rigidbody.AddForce(force / 2f, ForceMode.Acceleration);
I am using my physics helper class
public static class PhysicsTools
{
public static Vector3 GetMovement(Vector3 velocity, Vector3 force, float time)
{
return (velocity * time) + 0.5f * force * (time * time);
}
public static Vector3 GetMovementUpdateVelocity(ref Vector3 velocity, Vector3 force, float time)
{
Vector3 ret = (velocity * time) + 0.5f * force * (time * time);
velocity += force * time;
return ret;
}
}
Everything works fine, but if, and only if, I divide the force by two when applying it. My own simulation using PhysicsTools does not require such tampering.
Here's a picture of one of my tests, with the force factor applied to both the physics engine and the PhysicsTools simulation. You can see that the simulated lines go off into the distance, but not the actual projectile, which stays in its weird pentagram, as it should.
Here we can see it working as intended (still with the applied force reduced)
My question, why would I need to divide that damned force by two?

Well here folks is what happen when you make assumptions.
I assumed that ForceMode.Continuous meant that the force would be applied continuously through the frame. That is not the case.
The unity physics engine is incapable of any kind of continuous acceleration or parabola casting. Any object moves in straight lines, and AddForce simply modifies the velocity right then and there.
It turns out that simply dividing the force by two was enough to reset the starting velocity to my previous linear solution to the problem, and that the only reason that objects seemed to react outside of the polygon was that my bullet collider was much wider than I thought it was.
Please read this post for more information: https://answers.unity.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html
The only solution to the problem is to increase the physics framerate, or to use your own raycasting solution, which comes with a slew of other problems.

Related

Can't place rays from some start to some end

I have a start and end, i need to place an adjustable amount of rays on the line from start to end. But I can't figure out how to correctle place them. I almost finished, but rays is not placed at the end.
Green (2, 0, 2) for start and Red (2, 0, -2) for end with distance between 4
This is my result with 2 rays, the center ray need to be at the end (red ray), and next rays add should be at the center of start and end rays
What happens if i adding third ray, the end ray position isn't in use
And 5 rays. The first and the last rays need to be at the start and the end. Other 3 rays should to be between
This is my code:
public class VehicleAroundCast : MonoBehaviour
{
[SerializeField] private int sideRaysAmount;
[SerializeField] private Vector3 offset;
private void Update()
{
Vector3 startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
Vector3 endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
float dist = Vector3.Distance(startRayPos, endRayPos);
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
for (int i = 0; i < sideRaysAmount; i++)
{
float step = dist / sideRaysAmount * i;
Vector3 position = (transform.position + Vector3.left * offset.x + Vector3.forward * offset.z) + Vector3.back * step;
Debug.DrawRay(position, Vector3.down);
}
}
}
Following up from the clarification through the comment I'd do a couple of things a bit different. I'll just edit your code so that it should work as you'd need it to and explain it in code comments.
public class VehicleAroundCast : MonoBehaviour
{
[SerializeField] [Min(0)] private int intermediateRayCount;
// i renamed this field so the name is more descriptive, intermediate meaning between start and end
// to make sure that no number below 0 can be assigned i added the [Min(0)] attribute, a number
// below 0 would break the logic and wouldn't make sense
[SerializeField] private Vector3 offset;
private void Update ()
{
// we dont want to clog up our Update() method with tons of lines of code
// this serves better maintainability as well
CastRays();
}
private void CastRays ()
{
Vector3 startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
Vector3 endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
// if we dont have any intermediate rays we can save on the calculation that is necessary when we
// have intermediates, we just cast the the two rays and end the method by returning
if (intermediateRayCount == 0)
{
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
return;
}
// instead of using Vector3.Distance() we'll calculate the directional vector manually this way
// we can get not only the distance (directional vectors magnitude) but the direction to move
// along to space the rays as well
// subtarting the start position from the end position gives us the directional vector
Vector3 direction = endRayPos - startRayPos;
// a directional vectors magnitude gives the distance from start to end
float distance = direction.magnitude;
// after the distnace has been established we normalize the direction so that it's length is 1
direction.Normalize();
// we have at least two rays (start and end) so our total is the rays between start and end plus two
int totalRayCount = intermediateRayCount + 2;
// the space between the individual rays, we have to subtract one from the totalRayCount in order to
// place the the last ray at the end position (yes this could be optimized but i want to write out
// the logic fully so that it's clear what happens)
float spacing = distance / (totalRayCount - 1);
for (int i = 0; i < totalRayCount; i++)
{
// we can simply get the ray position by adding the direction multiplied by the spacing multiplied
// by the number of itterations we've gone through to the start position
// since the direction is normalized we can multiply it by the distance between rays to give it
// the length between two rays, if we then multiply it by the number of rays the current ray is,
// we get the distance as well as direction the current ray has to be placed away from the start
// position
Vector3 rayPosition = startRayPos + direction * spacing * i;
// i added some color so you can see start and end
if (i == 0)
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
else if (i == totalRayCount - 1)
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
else
Debug.DrawRay(rayPosition, Vector3.down, Color.white);
}
}
}
It's a simple logic mistake.
You wanted sideRaysAmount to be the amount of rays. But you are using it as amount of steps in between rays.
There is always exactly one step less than rays.
=> For the calculation of step you simply want to use
float step = dist / (sideRaysAmount - 1) * i;
so e.g.
var startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
var endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
var dist = Vector3.Distance(startRayPos, endRayPos);
var stepSize = sideRaysAmount == 1 ? 0 : dist / (sideRaysAmount - 1f);
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
for (var i = 0; i < sideRaysAmount; i++)
{
var step = i * stepSize;
var position = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z + Vector3.back * step;
Debug.DrawRay(position, Vector3.down);
}

I'm using C# (Unity) and can't put variables into my vector3

Full disclosure, I do not know C# at all. I've been coding this just using the internet. I decided to try learning Unity over the summer before I start my first year at university and have been working on walking controls.
I've been trying to make the character turn in the direction they're facing and while I can get them to go forward just fine. If I have them turn though, subsequently after that any attempt to make them go forward (even if I just have them return to a rotation of 0) end up with the error message "rigidbody.force assign attempt for 'Fox' is not valid. Input force is {NaN, 0.000000, NaN}."
Here's the code:
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
//private quaternion rotation;
public class PlayerController : MonoBehaviour
{
public Rigidbody rb;
public float moveSpeed = 10f;
public float rotationSpeed = 7f;
public float rotation;
private float xInput;
private float zInput;
private float directionInput;
public GameObject player;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
ProcessInputs();
if (Input.GetKeyDown("a"))
{
player.transform.Rotate(0f, -(rotationSpeed), 0f, Space.World);
}
if (Input.GetKeyDown("d"))
{
player.transform.Rotate(0f, rotationSpeed, 0f, Space.World);
}
}
private void FixedUpdate()
{
//Movement
Move();
}
private void ProcessInputs()
{
xInput = Input.GetAxis("Horizontal");
zInput = Input.GetAxis("Vertical");
}
private void Move()
{
//float xMove;
//float zMove;
rotation = player.transform.rotation.y;
if (rotation == 0)
{
float xMove = 0f;
float zMove = zInput;
rb.AddForce(new Vector3(xMove, 0f, zMove) * moveSpeed);
}
else if (rotation <= 90)
{
float zMove = (float)(Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Cos(90 - rotation)))) * (zInput * (1/(Math.Cos(90 - rotation))))) ));
float xMove = (float)(Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Sin(90 - rotation)))) * (zInput * (1/(Math.Sin(90 - rotation))))) ));
rb.AddForce(new Vector3(xMove, 0f, zMove) * moveSpeed);
}
//Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Cos(rotation)))) * (zInput * (1/(Math.Cos(rotation))))) );
//Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Sin(rotation)))) * (zInput * (1/(Math.Sin(rotation))))) );
//rb.AddForce(new Vector3(0f, 0f, zInput) * moveSpeed);
//rb.AddForce(new Vector3(xMove, 0f, zMove) * moveSpeed);
}
}
The problematic code is here:
float zMove = (float)(Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Cos(90 - rotation)))) * (zInput * (1/(Math.Cos(90 - rotation))))) ));
float xMove = (float)(Math.Sqrt((zInput*zInput) - ((zInput * (1/(Math.Sin(90 - rotation)))) * (zInput * (1/(Math.Sin(90 - rotation))))) ));
rb.AddForce(new Vector3(xMove, 0f, zMove) * moveSpeed);
There's no MathJax here (why??) so let me just drop the code for a moment and show you what you're doing:
sqrt( z^2 - (z/Cos)*(z/Cos))
But you can multiply out the second term:
sqrt( z^2 - (z^2/Cos^2))
And then you can factor out the z^2:
sqrt( z^2 * (1 - (1/Cos^2))
:EDIT: - Realized a slight issue with my argument here. Sine and cosine can range between -1 and 1, not just 0 and 1, but the rest of the argument still stands - Cosine squared or sine squared will only range 0 to 1, which will give you imaginary numbers.
:Original: (edited)
Now there's the problem. Cosine (or sine, for the other term) can only range between -1 and 1. So when you do something like Cos^2, the negative drops out and it can only range between 0 and 1. That means, when you have that term in the denominator, you get that 1/Cos^2 will range from 1 at the smallest to Infinity in the divide-by-zero case.
So you wind up with your (1/Cos^2) ranging [1 ... INF], and this means that you get two limits on your equation:
sqrt( z^2 * (1 - 1))
and
sqrt( z^2 * (1 - INF))
In the former case, you get zero, and in every other case you get the square root of a negative number. This gives you the imaginary number, which gets your the error you see:
rigidbody.force assign attempt for 'Fox' is not valid. Input force is {NaN, 0.000000, NaN}
because sqrt(-1) is Not a Number (NaN).
It looks like your trying to rotate the force to coincide with the forward and right directions, but you have unit vectors already pointing in those directions, transform.forward for z and transform.right for x, so just scale those by your moveSpeed, add them together, and add that force.
Vector3 zMove = rb.transform.forward * moveSpeed;
Vector3 xMove = rb.transform.right * moveSpeed;
rb.AddForce(zMove+xMove);
I'll caution you though that you're adding a FORCE here, not setting a speed, so this might not get you what you're after. I say that because the variable name in your code is moveSpeed. You might be happier setting
rb.velocity = zMove + xMove;
but it's your game and you can experiment with what you want :)

Find an angle to launch the projectile at to reach a specific point

So, Dani in his slightly new video -> "Making a Game, But I Only Have 3 Days" (https://youtu.be/S7Dl6ATRK2M) made a enemy which has a bow and arrow (at 5:39). I tried to recreate that but had no luck... I also can't find the website that he used... Today I found this https://physics.stackexchange.com/questions/56265/how-to-get-the-angle-needed-for-a-projectile-to-pass-through-a-given-point-for-t. It worked very well but still had problems if the target was far away and also it wasn't as accurate. The code so far is
float CalculateAngle()
{
float gravity = Physics.gravity.magnitude;
float deltaX = targetPositionMod.x - currentPosition.x;
float deltaY = targetPositionMod.y - currentPosition.y;
float RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
float RHSSecondPart = Mathf.Sqrt(
((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1);
float tanθ = RHSFirstPart - RHSSecondPart;
float angle = Mathf.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
The -angle is because the forward axis starts points up when the x-rotation is negative (Unity). Maybe the reason of this not working as intended is that I am not that good at this kind of Physics (Part of that is me being only 14). Maybe the problem is in the code, maybe it is the formula. Any help is appreciated.
Thanks...
Edit:
The Archer class is:
using UnityEngine;
using System;
public class Archer : MonoBehaviour
{
[SerializeField] float velocity = default;
[SerializeField] Transform target = default;
[SerializeField] GameObject arrowPrefab = default;
[SerializeField] float coolDown = default;
Vector3 targetPositionMod;
Vector3 currentPosition;
Vector3 targetPosition;
float countDown = 0f;
void Start()
{
countDown = coolDown;
UpdateVariables();
}
void Update()
{
UpdateVariables();
SetAngle();
ShootBullet();
}
void UpdateVariables()
{
currentPosition = transform.position;
targetPositionMod = Mod(target.position);
targetPosition = target.position;
targetPosition.x /= 10;
targetPosition.y /= 10;
targetPosition.z /= 10;
countDown -= Time.deltaTime;
}
void SetAngle()
{
Vector3 direction = targetPosition - currentPosition;
Quaternion lookRotation = Quaternion.LookRotation(direction);
Vector3 rotation = lookRotation.eulerAngles;
rotation.x = (float) CalculateAngle();
transform.rotation = Quaternion.Euler(rotation.x, rotation.y, 0f);
}
void ShootBullet()
{
if (!(countDown <= 0f)) return;
countDown = coolDown;
GameObject arrow = Instantiate(arrowPrefab, transform.position, transform.rotation);
Rigidbody Rigidbody = arrow.GetComponent<Rigidbody>();
Rigidbody.AddForce(transform.forward * velocity, ForceMode.Impulse);
}
double CalculateAngle()
{
double gravity = Physics.gravity.magnitude;
double deltaX = targetPositionMod.x - currentPosition.x;
double deltaY = targetPositionMod.y - currentPosition.y;
double RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
double RHSSecondPart = Math.Sqrt(
(((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1));
double tanθ = RHSFirstPart - RHSSecondPart;
double angle = Math.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
Vector3 Mod(Vector3 Vec)
{
if (Vec.x < 0) Vec.x -= 2 * Vec.x;
if (Vec.y < 0) Vec.y -= 2 * Vec.y;
if (Vec.z < 0) Vec.z -= 2 * Vec.z;
Vec.x /= 10;
Vec.y /= 10;
Vec.z /= 10;
return Vec;
}
}
Ok, as I can see, your implementation of formula from StackExchange is right, but you have to remember two things:
In unity there is a 3D world, so horizontal distance is not just pos1.x - pos2.x, but
Mathf.Sqrt( deltaX * deltaX + deltaZ * deltaZ ), where deltaX = targetPositionMod.x - currentPosition.x and deltaZ = targetPositionMod.z - currentPosition.z
In computer implementation you have no 100% accuracy of math, so some problems can appear because of computational accuracy. And it can have affect on big distances. You can try to use double instead of float or find another implementation for arctangent function (I think, this can really help). But try this (second) advice only if first didn't help. It's harder to implement and it slows computations a bit.
Algorithm:
Step 1: Set up a function that calculates the appropriate solution of a quadratic equation
a*x^2 + b*x + c = 0
double quadratic_root(a,b,c){
D = b^2 - 4*a*c
return ( - b - Math.Sqrt(D) ) / (2 * a)
}
Step 2: Input
current.x
current.y
current.z
target.x
target.y
target.z
velocity
gravity
Step 3: Calculate coefficients of the quadratic polynomial:
dist = Math.Sqrt( (target.x - current.x)^2 + (target.y - current.y)^2 )
a = gravity * dist^2 / (2 * velocity^2)
b = -dist
c = target.z - current.z + a
Step 4:
theta = Math.Atan2( quadratic_root(a,b,c), 1 )
Calculation behind the algorithm. You are in three space. The current position has coordinates
x = current.x
y = current.y
z = current.z
and the target has coordinates
x = target.x
y = target.y
z = target.z
Assume the angle between the initial velocity and the horizontal plane is theta. The magnitude of the projection of the distance between the current position and the target onto the horizontal $x,y-$plane is
dist = sqrt( (target.x - current.x)^2 - (target.y - current.y)^2 )
You are given the velocity magnitude velocity. Then, the speed with which the shadow (i.e. the orthogonal projection) of the arrow moves along the horizontal line between the source and the target is the magnitude of the shadow (i.e. the orthogonal projection) of the actual velocity
velocity * cos(theta)
The vertical speed of the arrow is then
velocity * sin(theta)
So the motion along dist follows the equation
dist = time * velocity * cos(theta)
and hence
time = dist / (velocity * cos(theta))
In the vertical direction, the motions is described by the equation
z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
You are interested in the time for which the arrow hits the target, which has vertical coordinate target.z, so
target.z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
The equation can be written as:
0 = - (target.z - current.z) + time * velocity * sin(theta) - time^2 * gravity / 2
We already know that
time = dist / (velocity * cos(theta))
so
0 = - (target.z - current.z) + dist * velocity * sin(theta) / (velocity * cos(theta)) - dist^2 * gravity / ( 2 * (velocity * cos(theta))^2 )
which can be slightly simplified to
0 = - (target.z - current.z) + dist * sin(theta) / cos(theta) - gravity * dist^2 / ( 2 * (velocity * cos(theta))^2 )
Because 1/( cos(theta)^2 ) = 1 + ( tan(theta) )^2 we obtain the quadratic in tan(theta) equation
a * ( tan(theta) )^2 + b * tan(theta) + c = 0
where
a = gravity * dist^2 / (2 * velocity^2)
b = - dist
c = target.z - current.z + a

Calculating Angular Velocity Needed to Travel a Certain Distance Via the Magnus Effect

Aight so I've been at this for several days and with how much it's slowing the project down I've made the hard decision to come beg the internet.
I'm working on a physics-based golf game in Unity and am working on implementing the magnus effect. This is my calculation for tangential force based on torque:
void FixedUpdate()
{
float _BallRadius = .0427f;
float _halfmass = Mathf.Sqrt(_BallRadius*.5f );
float _vv = new Vector3(GetComponent<Rigidbody>().velocity.x, 0,
GetComponent<Rigidbody>().velocity.z).magnitude;
//MAGNUS FORMULA (SIDESPIN ONLY)//
float _mag = -((1.429f * Mathf.Abs(_vv)* (2 * Mathf.PI * _halfmass *
GetComponent<Rigidbody>().angularVelocity.y))) * (_BallRadius * .5f);
Vector3 _xmagdir = new Vector3(GetComponent<Rigidbody>().velocity.x, 0, GetComponent<Rigidbody>().velocity.z);
GetComponent<Rigidbody().AddForce(Vector3.Cross(_xmagdir.normalized,Vector3.up)*_mag, ForceMode.Acceleration);
}
This works well enough, but now I need to figure out the correct amount to give the ball to get this result:
Diagram of Desired Curve
I've tried several different approaches, and I've come to the conclusion that this is mostly comes down to trigonometry. Here is where I'm at currently (this is before the tangential direction is applied to the final velocity vector):
Vector3 GetMagnusTorque(float Distance, Vector3 V_BallForce, float xdir)
{
float _BallRadius = .0427f;
float _halfmass = Mathf.Sqrt(_BallRadius*.5f);
Vector3 v = GO_Camera.transform.InverseTransformDirection(V_BallForce);
float HorizontalRange = Distance;
//THIS IS THE LATERAL DISTANCE WE NEED TO TRAVEL//
float opposite = (Mathf.Abs(Mathf.Tan(Mathf.Deg2Rad * xdir))) * (HorizontalRange*.5f);
//THIS IS THE TIME IT WILL NEED TO TAKE//
float time = ((2 * v.magnitude * Mathf.Sin((2 * (v.normalized.y )))) / -Physics.gravity.y);
//THIS IS THE SPEED THE MAGNUS EFFECT WILL NEED TO PRODUCE//
float linearSpeed =Mathf.Abs((opposite/time))*Mathf.Sign(xdir)Mathf.Abs((opposite/time))*Mathf.Sign(xdir)*(Time.timeScale+Time.fixedDeltaTime);
return GO_Camera.transform.TransformDirection((((linearSpeed /( 2 * Mathf.PI * _halfmass * (_BallRadius * .5f))) / 1.429f))*Mathf.Deg2Rad*GO_PGolfBall.transform.up);
}
Logic behind this
The result is very inconsistent based on xdir and V_BallForce, sometimes travels further, sometimes barely at all. This has been a pretty sobering adventure in discovering that I'm pretty bad at math.
If anyone has any advice for this issue I would forever be in your debt.
Update: Here is some visualization of the issue:
visualization
Update: I figured I should include a code sample of the function GetMagnusTorque is called from, to better put things in context:
void BallApplyForce()
{
//_rmult is a [0,1] value that is dependent on the accuracy of the player's swing//
float _rmult = GetRMult();
//GETS BALL READY BY ENABLING PHYSICS AND SETTING ITS ROTATION TO THE CAMERA//
GO_PGolfBall.transform.rotation = new Quaternion(0, GO_Camera.transform.rotation.y, GO_Camera.transform.rotation.z, GO_Camera.transform.rotation.w);
GO_PGolfBall.GetComponent<SCR_GOLFBALLCONTROL>().B_PhysicsActive = true;
//YDIR = PITCH, XDIR = YAW. V_ContactPoint is a [-1,1] Vector2 used similar to Mario Golf//
Vector3 _vdir = Vector3.zero;
float ydir = ((Mathf.Rad2Deg * DIC_Clubs[I_CurrentClub].LandAngle) + (-2.5f * (.1f) * (DIC_Clubs[I_CurrentClub].LoftAngle * Mathf.Rad2Deg)));
float _xdir = Mathf.Rad2Deg * (-(V_ContactPoint.x) * (DIC_Clubs[I_CurrentClub].LoftAngle)*3f);
_vdir.y = ydir;
_vdir = _vdir.normalized;
_vdir.y *= Mathf.Rad2Deg;
//MAX DISTANCE OF THE CLUB BEING USED TO METERS//
float _dist = ((DIC_Clubs[I_CurrentClub].MaxDistance * F_UPower) * _rmult) * .9144f;
//GET FORWARD AND UPWARDS VELOCITIES BASED ON THE DISTANCE GIVEN, XDIR ISN'T FACTORED FOR NOW//
float Vi = Mathf.Sqrt(_dist * -Physics.gravity.y / (Mathf.Sin(Mathf.Deg2Rad * _vdir.y * 2)));
float Vy, Vz;
Vy = Vi * Mathf.Sin(Mathf.Deg2Rad*_vdir.y);
Vz = Vi * Mathf.Cos(Mathf.Deg2Rad*_vdir.y);
GO_Camera.transform.eulerAngles = new Vector3(0, GO_Camera.transform.eulerAngles.y, 0);
Vector3 _velo = GO_Camera.transform.TransformVector(new Vector3(0f, Vy, Vz));
//CALCULATE VERTICAL ANGULAR VELOCITY, THIS DOESNT NEED TO FOLLOW ANY SORT OF PATTERN FOR NOW//
float _verRoll = Mathf.Sign(V_ContactPoint.y - .1f) * ((.7135f) * _dist) * Mathf.Sin(DIC_Clubs[I_CurrentClub].LoftAngle +
((Mathf.Abs(V_ContactPoint.y + .1f)) * (DIC_Clubs[I_CurrentClub].LaunchAngle))) * 60 * Mathf.Deg2Rad;
GO_PGolfBall.GetComponent<Rigidbody>().AddTorque(Vector3.Scale(GO_PGolfBall.transform.right, new Vector3(1, 0, 0)) * _verRoll, ForceMode.VelocityChange);
//CALCULATE HORIZONTAL ANGULAR VELOCITY//
Debug.Log("MAGNUS CALC:" + GetMagnusTorque(_dist, _velo, _xdir));
GO_PGolfBall.GetComponent<Rigidbody>().AddTorque(GetMagnusTorque(_dist, _velo, _xdir), ForceMode.VelocityChange);
//APPLY XDIR TO THE CAMERA ANGLE AND RECALCULATE VELOCITY//
GO_Camera.transform.eulerAngles = new Vector3(0, GO_Camera.transform.eulerAngles.y+_xdir, 0);
_velo = GO_Camera.transform.TransformVector(new Vector3(0f, Vy, Vz));
//APPLY VELOCITY//
GO_PGolfBall.transform.GetComponent<Rigidbody>().AddForce(_velo, ForceMode.VelocityChange);
}
Update 11/21: I'm currently in the process of faking it through trial and error. But for those still interested in solving this anomaly, I may have found in issue in how I'm calculating the direction to apply the side-spin derived force to the ball. Hitting the ball with a lob wedge, which has a maximum distance of 70 yards and a loft angle of 60 deg, the desired curve looks something like this: Lob Wedge This obviously makes no sense, because the ball flies behind the golfer for a short period of time. I'll report back with the final numbers and calculations, though it will not answer the question it could help point it in the correct direction.

Player is not moving toward target without unity built in functions

I am trying to move my target object towards my player using custom codes(without built-in function of unity like MoveTowards)
Vector3 displacment = target.transform.position - player.transform.position;
//you can also get magnitude directly through displacement.magnitude
float xMagnitude = displacment.x * displacment.x;
float yMagnitude = displacment.y * displacment.y;
float zMagnitude = displacment.z * displacment.z;
float customMagnitude =Mathf.Sqrt(xMagnitude + yMagnitude + zMagnitude);
directionToMove = new Vector3(displacment.x / customMagnitude, displacment.y / customMagnitude, displacment.z / customMagnitude);
//directionToMove = displacment.normalized;
Vector3 velocity = directionToMove * speed;
Vector3 moveAmount = velocity * Time.deltaTime;
target.transform.position += moveAmount;
I first got the displacement between two vectors than i get its direction and pass it to my position with speed. but its direction is not towards the player. what i am doing wrong?
If you want to move your target towards the player, the displacment must be :
displacment = player.transform.position - target.transform.position;
To generate a vector given the initial point : A (x1, y1, z1) and final point B (x2, y2, z2) the calculation is the following :
v = B - A = (x2 - x1, y2 - y1, z2 - z1);
So let me get this straight: You want to move the target towards the player, not the player towards the target. Is this correct?
If you want to move the target object towards the player, then the displacement should be player.transform.position - target.transform.position, not the other way around.
If you really don't want to use the Unity functions, then here's how I would do your code:
Vector3 displacment = player.transform.position - target.transform.position;
float xMagnitude = displacment.x * displacment.x;
float yMagnitude = displacment.y * displacment.y;
float zMagnitude = displacment.z * displacment.z;
float customMagnitude =Mathf.Sqrt(xMagnitude + yMagnitude + zMagnitude);
//multiplying vector by scalar works the same,
//and is more readable (possibly faster too)
directionToMove = displacement * 1f/customMagnitude;
Vector3 velocity = directionToMove * speed;
Vector3 moveAmount = velocity * Time.deltaTime;
target.transform.position += moveAmount;

Categories