#Potential and kinetic energy stuff
1 messages · Page 1 of 1 (latest)
I honestly prefer english these days anyways. The mods can't verify what we talk if we use any other language so better keep it at english
jees
So the idea is basically that if we assume there's no other forces in play, the potential energy (energy based on height) + kinetic energy is constant (energy can convert between the two but no energy ever disappears)
The formula for potential energy is m * g * h (mass times -9.81 times height based on some chosen level). From the height change we can calculate the amount of change in potential energy which should then all transfer to kinetic energy. Does this make sense at all?
@worthy dragon Do you want me to further explain something or is this all clear so far?
I think i follow until the last sentence. Maybe if you can give an example related to the games situation it could help. Im also tired so ill look at this again tomorrow
Thanks already for helping btw
Like if the ball is going up in lets say 45° angle following the spline. It should lose speed as gravity is pulling it back, we can ignore friction at least for now. It also gains potential energy since its rising. So if it doesnt have enough kinetic energy to reach the top, it means the potential energy overcomes the kinetic and starts pulling it back down? Im not sure if its like this. Also im not sure how to apply this to code. Im kinda new to coding
As far as code goes it'd just be something like I shared here: #⚛️┃physics message
you check what the difference in your height is from last frame. That's a difference in potential energy. Depending on what that difference is you add or remove speed (aka kinetic energy)
for example if we find ourselves 1 meters higher than last frame that means we have gained potential energy. The only way to gain potential energy in this closed system is to lose kinetic energy. So we reduce our speed.
On the flipside if we found we are lower than we were last frame we lost potential energy. That means we need to increase kinetic energy to keep things balanced. In other words we went downhill and picked up speed.
If I'm not mistaken, the right formula for the updated speed after each frame/physics step would be sqrt(v * v - 2 * g * heightDifference) where v is the current speed and g is the gravitational acceleration (9.81). When the formula inside the square root gives a negative value, you know the ball should change direction. I just put some formulas to geogebra and it is too late for my brain to confirm any of that. This formula always keeps the speed positive so you would want to store the magnitude of the speed and the sign/direction separately
correct but in my example code I pretty much folded the -2g into a generic constant variable
Ohh ok that makes sense!!
That sure is a constant. The square root and the squared velocity are important components in the equation though
I think this would work although there's one edge case that needs to be taken into account. That is, there is potentially possibility that the ball sits at a slope with 0 velocity and therefore 0 heightDifference and won't ever get moving (could happen in case when the ball changes directions for example). There are ways to fix this but I just realized something stupidly simple.
Given the tangent of the curve at the position of the ball (which is trivial to get with unity's spline system), the acceleration along the tangent caused by gravity would just be the dot product between the gravitational acceleration vector and the tangent so just Vector3.Dot(Physics.gravity, tangent). From the acceleration getting to velocity change is also trivial, just multiple by time delta (Time.fixedDeltaTime if in fixedUpdate).
With this approach it becomes super trivial to update the velocity of the ball every frame (preferably in fixedUpdate) to respect the gravitation. This way there's also no way the ball ever gets stuck in a slope. The sign of the velocity delta should also be automatically correct if the tangent is defined as tangentFromSpline * Mathf.Sign(currentVelocity)
This approach basically relies on the fact that the acceleration along the path caused by gravity can be directly calculated from the slope of the rail (e.g. 0 deg slope = 0 acceleration, 90 slope = -9.81 acceleration). Luckily with the help of dot product, there's no need to do any complicated angle calculations or advanced math in this case
Im not sure how to apply this to my script that I wrote today
using UnityEngine.Splines;
public class SplineController : MonoBehaviour
{
private SplineContainer spline;
private Collider ballCollider;
private Rigidbody rb;
private bool followingSpline;
private float positionOnSpline;
private float splineMoveSpeed;
private int splineMoveDirection; // 1 = forward, -1 = backward
void Start()
{
rb = GetComponent<Rigidbody>();
ballCollider = GetComponent<Collider>();
ballCollider.enabled = true;
}
void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("SplineTrigger") || followingSpline) return;
//ignore if already following a spline
spline = other.transform.parent.GetComponent<SplineContainer>();
//get spline component of collider parent object
followingSpline = true;
ballCollider.enabled = false;
splineMoveSpeed = rb.linearVelocity.magnitude;
//set speed of moving along spline to balls current velocity
Vector3 startPos = (Vector3)spline.EvaluatePosition(0f);
Vector3 endPos = (Vector3)spline.EvaluatePosition(1f);
positionOnSpline = Vector3.Distance(transform.position, startPos) < Vector3.Distance(transform.position, endPos) ? 0f : 1f; //ternary operator: condition ? value_if_true : value_if_false;
// Set entry position to closest end (start or end)
splineMoveDirection = Vector3.Dot(rb.linearVelocity.normalized, (Vector3)spline.EvaluateTangent(positionOnSpline)) < 0 ? -1 : 1;
//get balls direction on spline (-1 to 1)
}
void FixedUpdate()
{
if (!followingSpline) return;
else
{
positionOnSpline += splineMoveDirection * (splineMoveSpeed / spline.CalculateLength()) * Time.fixedDeltaTime;
Vector3 ballSplinePosition = (Vector3)spline.EvaluatePosition(positionOnSpline);
//find 3d position of ball on spline location (0 to 1)
Vector3 splineTangent = ((Vector3)spline.EvaluateTangent(positionOnSpline)).normalized;
//get direction of spline (tangent) on spline location
rb.MovePosition(ballSplinePosition);
rb.linearVelocity = splineMoveDirection * splineMoveSpeed * splineTangent;
//update velocity so when ball gets released its direction is correct
if (positionOnSpline < 0f || positionOnSpline > 1f)
{
followingSpline = false;
ballCollider.enabled = true;
}
}
}
}
sorry if its hard to read, maybe the comments help
i have no idea if this method of doing this is any good, just what i could figure out
@solid flax could you take a look? or if its too much to read can you help someway?
I will take a closer look bit later. What does the code do so far? Does it work as far as you have implemented it?
So right now the code just moves the ball along the spline after hitting a trigger collider and then exits the spline at the other end.
In OnTriggerEnter() I get get the ball velocity into splineMoveSpeed. Then I find the nearest end of the spline and put the ball there.
In FixedUpdate() I move positionOnSpline by (splineMoveSpeed / spline.CalculateLength) * fixeddeltatime. I then move the rigidbody with MovePosition to that positionOnSpline.
I also change the linearVelocity to match the direction of the spline so when the ball exits the spline it moves to the correct direction.
I also disable the ball collider when its on the spline so it doesnt hit anything.
This doesnt take gravity into account at all yet
The only thing left is to constantly update splineMoveSpeed according to either of the ways explained. I Just implemented the latter option and it seems to work (also confirmed that at the lowest point all of the potential energy is correctly converted to kinetic energy even though neither is accounted in the formula). The attached video is recorded at scale where the ball is 1 meter across so it won't obviously look exactly like the one you will be getting. I would suggest implementing the latter given how incredibly easy it was (literally two lines of code). That way you also don't really need splineMoveDirection at all, instead you can just let splineMoveSpeed be either positive or negative which will define the direction of movement
Literally all you need to do is figure out the tangent vector ("forward" direction along the spline) at the current position using spline.EvaluateTangent (which apparently isn't normalized so you must normalize it to make it 1 long, otherwise the formula won't work) after which you can update the velocity by doing velocity += dot(gravity, normalizedTangent) * fixedDeltaTime
I tried to figure it out before you sent that message, i came up with this. Isnt it basically what you said? Its not working though.. the ball just slows down to a crawl when it hits the spline
Oh i made it += instead of = and now theres movement at least.. not sure if the gravity works tho
Yeah the gravity works and it turns around!
oh yeah, didn't spot that. glad you got it working. Now I would just get rid of splineMoveDirection altogether, I don't think you need that. Btw. what I said about tangentFromSpline * Mathf.Sign(currentVelocity) in one of my earlier messages is just wrong and not needed at all.
I tried removing splinemovedirection but it lead to some weird interaction when the ball returns back to the start of the spline it bounces back to the spline instead of going out. Probably its because it hits the enter trigger again, so would need to make sure it can exit the spline without hitting it again
Idk if the best way to avoid it hitting the enter trigger again would be to add a timer after exiting splines before one can be entered again? Doesnt seem very reliable solution
If it only happens when splineMoveDirection exists, it is likely just that you are giving rb.linearVelocity the wrong velocity (back towards the rail)
Oh this was recorded actually without removing the splinedirection. But as you can see there is a problem anyway with the collider triggers
Honestly I don't see a reason to constantly update rb.linearVelocity, you can just apply it in if (positionOnSpline < 0f || positionOnSpline > 1f) because that's actually the only case where the velocity matters
yeah youre right
heres the bouncy boy
shit
After moving the line to the if statement the ball is jittering a lot whil on the spline. Putting it back to fixedupdate fixes it
The easiest solution that came to my mind would be to just make sure the rail is never entered on OnTriggerEnter if the ball is moving away from the rail. How exactly to recognize that I'm not sure, my brain's not functioning right now, I'll get something to eat now
Oh, that must be because the rb of the ball tries to fight against your will (MovePosition isn't exactly the best way to move non-kinematic rigidbodies). Because you don't necessarily need any physics during the ride, you should just set the rb to kinematic while on the ride and activate it again on the other end
I removed the code that disables the collider of the ball when its on the spline and now it doesnt trigger the collider when exiting. I dont really understand why it works like that
I think i tried that at first but had some kind of issue. I will try again
Oh this is why the kinematic thing didnt work. Well now I know that I dont need to set the velocity when in the spline so I will move it again to the end only
I think that is what you should do though. Keeping the ball active while you are moving it via transform (basically what MovePosition does for non-kinematic bodies) isn't a great idea
Ah but actually its coming from a different script D:
I mean i could disable the ballcontroller script for the duration of the spline ride I think?
Nope, now it wont ride the spline just stops when it hits the trigger collider
Do you still have the MovePosition code in the place of the linearVelocity? ~~I will get something to eat now, will come back to you later ~~
Idk if it would be best to go to the ballcontroller script and add if(!followingSpline) where the error is coming from
No the rb.linearvelocity is set in the if clause when at the start or end of spline
but are you still updating the position of the ball in fixedUpdate using MovePosition? Obviously you need to move the ball somehow if you want it to not just stay in place during the ride
The reason this happens is because you are using OnTriggerEnter which is only fired once when colliding. That collision will happen while the ball is coming towards the end of the rail but followingSpline is still true so that hit will be ignored. Althought that seems to work, I would still be bit worried about some edge cases where depending on the positioning of the collider of the entry point, you could hit the collider only after leaving the rail and therefore be sent back to it. I could also see an case happening where the ball would be coming flying in from the direction of the rail (not on the rail) like visualized in the image and still get scooped in by the rail even though the ball is clearly moving away from the rail. Just to be safe, it would be better to make sure the ball is never catched by the rail if it isn't moving towards it
I'm also noticing maybe more likely edge case that could happen when the ball is shot from lower platform to a higher one through a rail. If the speed is just right, the ball may just barely make it to the higher side and stop before exiting the collider for the rail entry. If you hit your next shot towards the rail, the rail would not be recognized at all because you would be inside it already. Even likelier scenario maybe would happen if the ball is shot towards the rail at low enough speed that it would end up just barely making it to the rail, taking a turn there and ending up just slightly outside the same entry point it came from still within the collider (and if the force was simply too low the first time, the player is likely to shoot towards the rail again).
To avoid these issues altogether, it would be better to just use OnTriggerStay to continuously get hit information from the entry point and only accept the hit when the ball is moving towards the rail. To figure out whether the ball is moving towards the rail, you could simply use dot product to compare the velocity of the ball to the tangent of the spline at the end point. Note that whether positive dot product indicates towards or away from the rail depends on which side of the rail you are (at the 0 side or the 1 side)
Enough for today. Let me know if anything is unclear here, I will happily elaborate
Yeah that seems like a good solution to avoid triggering the spline unwanted
Though from what ive noticed in my testing the ball seems to work fine right now. I moved the rb.linearVelocity back in to avoid the stutters, and fixed some direction setting issue when entering from the other end, but it seems to work now
I tried to add a multiplier to slow the ball down slowly while on the spline to simulate friction, but it didnt work as I had hoped
It also avoids the case where the trigger is ignored when it's not exited after a ride. When it comes to the friction, I would consider trying to simulate the rolling resistance F=C*N instead which is bit tricky because you need the "normal force" N but it should be doable. I'm thinking of maybe calculating the normal force in two parts by calculating the force caused by gravity and one caused by the centrifugal force (using the curvature of the rail and the velocity of the ball) and adding these two together to form the total "normal force". This way the ball would lose more momentum in tight curves and also would not lose any momentum if it is basically freefalling on the rails (if the rail is curving downwards at a rate that follows the natural fly path of the ball at that speed)
I dont really understand what that means
Oh sorry i meant to follow up to that message
I can understand that the normal is the combination of gravity and the centrifugal force, but what is the C in F=C*N? And how do I calculate the centrifugal force using velocity and the curvature of the spline (the tangent?)
I coded this solution with the help of AI
void FixedUpdate()
{
if (!followingSpline) return;
else
{
Vector3 ballSplinePosition = (Vector3)spline.EvaluatePosition(positionOnSpline);
Vector3 splineTangent = ((Vector3)spline.EvaluateTangent(positionOnSpline)).normalized;
Vector3 splineTangentForward = ((Vector3)spline.EvaluateTangent(positionOnSpline + 0.01f)).normalized;
//get spline tangent on spline position + 0.01f
float splineCurvatureRadius = 1f / (Vector3.Cross(splineTangent, splineTangentForward).magnitude / 0.01f);
//get spline curvature by crossing splinetangents, get the curvature by 1/curvature
float centrifugalForce = (splineMoveSpeed * splineMoveSpeed) / splineCurvatureRadius;
//F = m^2 / r
Vector3 splineNormal = Vector3.Cross(splineTangent, Vector3.up);
float normalForce = Mathf.Abs(Vector3.Dot(Physics.gravity, splineNormal) + centrifugalForce);
//idk why we dot the gravity and normal, but combine that and the centrifugal force, N = mgcos(0) + F
float rollingResistance = rollingResistanceCoefficient * normalForce;
//apply coefficient to normal, bigger coefficient = more slowing down
splineMoveSpeed -= Mathf.Sign(splineMoveSpeed) * rollingResistance * Time.fixedDeltaTime;
//apply normal force to splinemovespeed
splineMoveSpeed += Vector3.Dot(Physics.gravity, splineTangent) * Time.fixedDeltaTime;
positionOnSpline += (splineMoveSpeed / spline.CalculateLength()) * Time.fixedDeltaTime;
rb.linearVelocity = splineMoveSpeed * splineTangent;
rb.MovePosition(ballSplinePosition);
if (positionOnSpline < 0f || positionOnSpline > 1f)
{
followingSpline = false;
//rb.isKinematic = false;
}
}
}
C is just a constant defining the amount of resistance (just a multiplier you can use to control the amount of resistance). According to Wikipedia for example "Hardened steel ball bearings on steel" would have C of 0.0010 to 0.0015, you can obviously use whatever feels good for the game
The code seems to work alright, it feels a bit weird but i cant put my finger on what feels wrong
tho I think for now its good, Ill come back to it if necessary
I can't really make much sense of the way splineCurvatureRadius is calculated, maybe it works maybe it doesn't. There's no need to do any of that though since the curvature radius can be directly accessed from 1f / spline.EvaluateCurvature(t)
Ohh alright ill that to optimize
I think it gets the curvature by calculating the difference between tangents at current position and current position + 0.01f via vector3.cross. I mean I dont really understand it, I think I got a 6 from the vectors course in lukio haha
What is the (t) in evaluatecurvature?
The point you want to evaluate the curvature at. positionOnSpline in our case
Thats what I guessed but it gives me this error:
The type 'UnityEngine.Splines.SplineContainer' cannot be used as type parameter 'T' in the generic type or method 'SplineUtility.EvaluateCurvature<T>(T, float)'. There is no implicit reference conversion from 'UnityEngine.Splines.SplineContainer' to 'UnityEngine.Splines.ISpline'.
That's what I figured as well, that's definitely an approximation by sampling at two positions though I don't really know how it is derived to the cross product in this case so no idea whether that actually works
Well it definitely slows the ball down
But your solution seems more elegant, tho Im struggling to figure this error out
Do I need to convert it to whatever ISpline is?
Its a bit weird that I can use EvaluatePosition and EvaluateTangent with positionOnSpline but not EvaluateCurvature
There's two ways, apparently spline in your case is the container so you can either do spline.Spline.EvaluateCurvature(t) or SplineUtility.EvaluateCurvature(spline.Spline, t)
Don't really know why that is but the Spline of the SplineContainer does have more functions than the container itself
How did you figure this out?
spline.Spline.EvaluateCurvature worked btw
The documentation is your best friend https://docs.unity3d.com/Packages/com.unity.splines@2.4/api/index.html
I feel like I dont know where to look inside the manual to find what i need
Btw just wanna say huge thanks to you
You can see that the variable you made for the spline is of type SplineContainer, if you go to that page, you will see all it's methods and for example that it has Spline property that returns object of type Spline which documentation reveals again it's functionalities. Not the easiest thing to do without knowing where to look for
Also I never implemented anything to avoid the trigger when exiting the spline and I havent noticed anything wrong yet. I think the only issue I have is if the trigger is hit from the side it jumps to the spline unrealistically, but I think that can be just solved by not putting the trigger in a spot where it can be hit from the side
It should only happen in a rare edge case as explained earlier but it is possible nonethless (let me know if you want me to elaborate on that). I'm not sure what you are exactly referring to with the unrealistic jump but there are obviously ways to fix it regardless outside the level design too
I guess its not a jump but like if the ball is coming from a sharp angle towards the spline and hits the trigger it smoothly moves up the spline, while in reality it would not get into the rail/spline and just bounce off since its not lined up with the railing
I guess could like check if the balls direction is aligned with the splines tangent at the entering position and only let it onto the spline if the angle is similar
I'm also wondering whether it would look fine if you gave the ball the initial velocity in the rail based on the component (of the velocity vector) that points towards the railing. That way if the angle is high, the ball would lose most of it's momentum as if it slammed the side wall of the rail
I think that might look a bit weird
fair enough
that's fair. What I'm wondering now is whether you could have some kind of colliders at both sides of the entry to work as the collider of the railing so that the ball would actually just collide with the red collider and not hit the trigger at all if it comes at too high angle
Yeah that could work too
I think that would look really natural if you get the positioning of the trigger just right
Btw. I don't think this Mathf.Abs(Vector3.Dot(Physics.gravity, splineNormal) is right either. This would give 0 resistance due to gravity when the ball is rolling on a horizontal path even though it should have maximum resistance then. I did try coding the resistance code myself earlier today and I got it pretty much working. I might share that when I get it fully functional
I did Debug.Log(Mathf.Abs(Vector3.Dot(Physics.gravity, splineNormal))); and actually it returns 0 always
Oh true, I just tested in my head the scenario where the track is horizontal but that is actually true for any orientation
shouldn't be that way
I dont really understand this part of the logic. Why am I even calculating the gravity in relation to the normal? Can't I just do normalForce = Mathf.Abs(9.81 + centrifugalForce);?
That's not the right (or accurate should I say) way for two reasons.
- the slope of the rail matters, if the rail is horizontal, there should be normal force caused by gravity, if the rail is vertical, there shouldn't (gravity isn't pushing the ball against the rail).
- Treating the two components of the normal force as numbers isn't necessarily correct, they should be vectors. This can be seen from the illustration that I drew. In that, lets imagine that the rail is in the shape of parabola and the starting speed V is such that even if the rail didn't exist, the ball would follow the red path. In this case there should be no normal force (the ball is basically freefalling so it shouldn't push against the rail) even though adding the two force magnitudes as numbers would give some normal force.
I'm not sure if either of these are really necessary for the level of realism that you are going for but in reality this way at least to me seems like the correct formula
So this is what I ended up with. From the tests that I made, it seems to work correctly, also in the case where the two forces cancel each other out:
//acceleration caused by gravity along the normal, works because of... vector math :)
//accelerationAlongTangent = math.dot(Physics.gravity, tangent), same is used for updating the velocity hence the variable
float3 gravityNormalAcceleration = tangent * accelerationAlongTangent - (float3)Physics.gravity;
//direction from the track towards the curvature center (direction of centripetal force)
//positionOnSpline = the position vector of the ball
float3 curvatureCenterDirection = math.normalize(spline.EvaluateCurvatureCenter(t) - positionOnSpline);
//from the formula F=mv^2/r, divided by m to get acceleration
//velocity = velocity of the ball, t = distance along the spline?
float3 centripetalNormalAcceleration = curvatureCenterDirection * (velocity * velocity * spline.EvaluateCurvature(t));
//if the bezier is straight, some of the calculations will result in NaNs, for straight beziers there's no curvature and no centripetal acc either
if (!math.all(math.isfinite(centripetalNormalAcceleration)))
{
centripetalNormalAcceleration = float3.zero;
}
//acceleration caused by the normal force
float3 normalAcceleration = gravityNormalAcceleration + centripetalNormalAcceleration;
float rollingResistanceAcceleration = rollingResistanceCoefficient * math.length(normalAcceleration);
//remove or add to the velocity to make it move towards 0
velocity = Mathf.MoveTowards(velocity, 0f, rollingResistanceAcceleration * Time.fixedDeltaTime);
Note that I'm primarily using Unity.Mathematics in place of Vector3s because the Splines API operates on float3s but casting everything to Vector3 and doing math.length(normalAcceleration) -> ((Vector3)normalAcceleration).magnitude etc. would be totally fine too (though this !math.all(math.isfinite part at least you would have to do yourself since Vector3 doesn't have such methods, you could use System.Single.IsFinite for that though)
Well i managed to implement your code but im not gonna lie to myself that i would understand it
The way the acceleration caused by gravity is calculated is bit cryptic I'm afraid. tangent * accelerationAlongTangent is basically the acceleration along the rail direction (green arrow), what we want though is the component of gravity perpendicular to the rail (blue arrow). This is why there is the subtraction between the vectors. The normal force (acceleration actually in this case) though is pointing in the opposite direction to the blue arrow, hence the subtraction is done in different order than in the image.
Centripetal force (opposite of centrifugal force) can be calculated by m*v^2/r (mass, velocity and radius of the curve). There I actually did a trick which I maybe shouldn't have done for clarity: instead of dividing by the curvature radius, I multiplied by the curvature which works because curvature = 1 / curvatureRadius. I also don't multiply by the mass of the ball here, because I actually want the acceleration, not the force (F=mv^2/r=ma -> a=F/m -> a=v^2/r). The direction of the centripetal force is calculated from the curvature center and the position of the ball (the centripetal force points towards the curvature center from the ball).
The two acceleration vectors are added together and the magnitude/length of the result vector is used to figure out how much resistance there should be. The if statement is there just to take care of special case where if the ball is on a straight rail, there would be no curvature in which case the curvature center would be infinitely far away and cause NaNs or Infinitys as answers.
Alright that actually helps a bit to understand
I also did a ray to visualize the force being applied which helps also to understand
Debug.DrawRay(ballSplinePosition, -splineTangent.normalized * rollingResistanceAcceleration, Color.red);
As expected when in basically freefall the force is tiny but doing a sharp turn it increases a lot
you can also draw the rays of the individual components, gravityNormalAcceleration and centripetalNormalAcceleration separately to see how those contribute to the total force
Btw is there an advantage in using Mathf.MoveTowards(0f) instead of Mathf.Sign
I guess its one calculation less
In most cases no, though if the velocity is very close to zero, the resistance force could actually flick the sign of the velocity. Lets say the velocity was 0.01 and the deceleration causes the velocity to decrease by 0.05, you would end up getting a velocity of -0.04 if you just subtracted (or added depending on the sign) from the velocity. This isn't very realistic though because the rolling resistance should always resist movement, not give it momentum in the opposite direction. MoveTowards is guaranteed to never overshoot so if the velocity was 0.01 and we moved it towards 0 by 0.05, we would end up at exactly 0. Doesn't really make much of a difference in most cases but something that I figured would make the code easier to read while also remove this unwanted behaviour
Right