Put these constant variables at the top of your script. I used an all capitals naming convention since they are constants in this code.
local RIGHT_VECTOR = Vector3.new(1, 0, 0)
local UP_VECTOR = Vector3.new(0, 1, 0)
local BACK_VECTOR = Vector3.new(0, 0, 1)
For this code I tried to work in all of the variables you were using, including RandomAngle. You might need to fiddle with it a little though.
Part.CFrame = CFrame.fromMatrix(TargetPos, SurfaceNormal:Cross(BACK_VECTOR).Unit, SurfaceNormal, SurfaceNormal:Cross(RIGHT_VECTOR).Unit) * CFrame.fromAxisAngle(UP_VECTOR, math.rad(RandomAngle))
Let me know if something is amiss and I will see what I can do after I get back from work. Good luck!
Edit for those curious. Here is how this works.
A CFrame matrix is a really annoying 4x4 matrix of numbers, but only 12 of the 16 numbers in the matrix are important. The numbers come in four groupings of three, and they can be represented by Vector3s. The first is position. Every CFrame of course has a Vector3 to describe position. The second V3 describes where the right side of the part is pointing. If this number is 1,0,0
, then so far the part is in its original orientation since the X (right) side of the part is pointing in the positive X direction. The third V3 describes the Y (top) side of the part. If this number is 0,1,0
then the part is so far unrotated. If the number is 0,-1,0
then we can ssume that the top side of the part is facing downwards. The fourth V3 describes the Z (back, not front) direction.
We know the position, that’s TargetPos
in your code. We also know which direction the top side of the part needs to face. That information is your variable SurfaceNormal
which is conveniently a unit vector (magnitude of 1) which describes a direction. So far so good, we have a position and a significant direction. Your code did that well enough. But we need another direction to keep it from rotating around. I used the static vectors for right and back, 1,0,0
and 0,0,1
. The problem here is that all three axes need to be perpendicular to each other, and if we used a raw 1,0,0
then we would have problems whenever the water wasn’t perfectly level.
The solution is the cross product. The cross product creates a perpendicular vector from two other vectors. If we did use the ideal 1,0,0
and 0,1,0
, then the cross product would be the missing third vector, 0,0,1
. In our case however, it took one of these constant vectors and the upwards vector to create our third vector. If our right vector was 1,0,0
and the waves beneath us were rotated 45 degrees to be 0, .707, .707
, then the cross product would tell us that the Z vector should be 0, .707, -.707
. The numbers themselves don’t need to make a tremendous amount of sense, but it just allows us to add some extra information to keep the part from rotating, and the cross product allows us to keep the directions perpendicular to each other.
After this, I used fromAxisAngle which uses a form of advanced Thaumaturgy to rotate the part around the axis specified. You can just use CFrame.Angles instead, but I avoid XYZ rotations like the plague it is.