There are several ways to handle this ability, and the path you take will ultimately come down to how your jump is designed.
First things first: be wary of OnCollisionEnter. The solution will work if your tags are setup properly, but would require additional tag filters if you had platforms tagged as the floor. If the tags weren’t setup correctly you would hit your head on the platform, tagged as floor, and your jump would reset. Not what you’re after.
One way to fix this problem is to use a physics raycast that aims downwards. If necessary, you could use an Overlap box instead, this may be better as you can then still jump if you’re right on the edge of a platform. Have a peak at the API, Unity - Scripting API: Physics.BoxCast
There are a ton of detection functions to choose from.
These casts would be done in the downwards direction, Vector3.down and you can use a collision mask to filter the cast. Using a mask allows you to cull out certain physics layers using the casting function, rather than constantly checking for tags in OnCollisionEnter, although the messages would be filtered by the collision matrix anyway.
In terms of code, it would be something along the lines of:
public class Playermovement : MonoBehaviour
public Rigidbody rb;
public float forwardForce = 200f;
public float sidewaysForce = 250f;
public float jumpHeight = 250f;
public float rotationForce = 50f;
//private so you can't change it externally by accident, use a getter to see it.
private bool isGrounded;
//do checks first, so if you need it later, it's all done
Vector3 feetPos = transform.position + offset; //offset is defined by you, this is just a safety check to ensure that the ray doesn't hit the player. You could avoid offset completely by using a layer mask, with the player being on its own layer.
Ray myRay = new Ray(feetPos, Vector3.down); //the origin and direction will differ depending on needs
float maxDistance = 2; //just a guess, tweak as needs dictate
int layerMask = LayerMask.GetMask("Floor", "Any other jumpable things"); //The mask used will depend on your project, also may want to cache on Awake so you're not constantly remaking it.
isGrounded = Physics.RayCast(myRay, maxDistance, layerMask); //true if the raycast hits something, should be only 'ground' objects if the mask is setup correctly
//check boxcast info if you'd rather use a box cast, similar principle just a few additional params
rb.AddForce(0, 0, forwardForce * Time.deltaTime);
rb.AddForce(sidewaysForce * Time.deltaTime, 0, 0);
transform.Rotate(-Vector3.forward * rotationForce * Time.deltaTime);
rb.AddForce(-sidewaysForce * Time.deltaTime, 0, 0);
transform.Rotate(-Vector3.back * rotationForce * Time.deltaTime);
bool movingUpwards = rb.velocity.y > 0;
//moving upwards can be used to prevent the player from hitting the jump button again if they just pressed it, as a force is applied the moment you press the button so if the velocity is > 0, then you're moving upwards. This solution may not work with moving platforms, going up. You could use a timer coroutine instead, start the coroutine the moment you press the button and don't let it be pressed again until it finishes, have a 'finished jumping' flag, false when just pressed, true when finished and 'time till next press' variable, defines how long until next press.
if (Input.GetKeyDown("w") && isGrounded && !movingUpwards)
rb.AddForce(0, jumpHeight, 0);
One final thing before concluding, may may want to use the input checks in Update. Fixed update is called consistently, however, you want to detect the press immediately and, unless I’m mistaken, Unity updates the input stuff, in the Update loop. As a consequence you may miss the press in Fixed update.
Hopefully this will lead you in the right direction.