Hello guys. I’m trying to make water. (got inspired by another topic). But the problem I encountered is that IDK how I can calculate water flow direction, if it’s on mesh:

Main problem here isn’t that IDK how to calculate that angle, I can, but IDK how to get face’s angle.

Is there any way which I can use to get face direction vector?

Have you ever heard of Trigonometry?

To calculate the angle, you can use either cosine (cos), sine (sin), or tangent (tan). If you use sine, you need to divide the opposite side by the hypotenuse. For cosine (which I recommend using in the formula), it’s adjacent side divided by the hypotenuse. For tangent, you divide the opposite side by the adjacent side.

I will make an imaginary triangle for you to understand

The calculation of Alpha (that curly a) would be:

cos Alpha = b (adjacent side) divided by c (hypotenuse)

a’s length would be equal to the face, directly pointing down to flat land you drew there. Same goes for c’s length, only being diagonal.

**You would have to use raycast to calculate the distance from the bottom (b) and diagonal (c). I am not really sure on how to do that tho’.**

But your calculation in Roblox would be this now:

```
local AdjacentSide = "InsertNumber"
local Hypotenuse = "InsertNumber"
local Angle = math.acos(AdjacentSide / Hypotenuse)
print(Angle)
```

To get the angle you just need to do:

```
local function getAngle(vectorA: Vector2, vectorB: Vector2): number
local dotProduct = vectorA:Dot(vectorB)
return math.acos(dotProduct / (vectorA.Magnitude* vectorB.Magnitude))
end
```

This is a mathematical formula so I don’t think there is any need to explain anything. For getting the actual vectors, `vectorA`

is just the `part.CFrame.LookVector`

property, for the other vector, you would shoot a raycast and assuming it hit something, you would do:

```
local raycastResult = ...
local normalVector = raycastResult.Normal
local v1 = Vector3.new(1, 0, 0) -- A random vector
local v2 = Vector3.new(0, 1, 0) -- Another random vector
-- Perform the Gram-Schmidt process to orthogonalize v1 and v2 with respect to normalVector
v1 = v1 - (normalVector:Dot(v1) / normalVector:Dot(normalVector)) * normalVector
v2 = v2 - (normalVector:Dot(v2) / normalVector:Dot(normalVector)) * normalVector
-- Now, v1 and v2 are orthogonal to normalVector.
-- You can choose either v1 or v2 as the "sliding" vector, aka vectorB.
local vectorB = v1
```

Now just call the `getAngle`

function and it should work. This may or may not work I don’t really know so make sure to test and not just think it’s 100% working. I’ve never done such thing before so yea, good luck!

You fixed the *easy* problem here, also this will only work if this is a right-angle triangle which most probably isn’t going to be the case.

Feels a bit rude; not saying it is but just letting you know.

and @msix29, I think I described my problem incorrectly. I know how I can calculate angle, but IDK how I can get face angle, because it’s possible that droplet will slide on mesh part, and not pre-defined BasePart.

In short: I want to get “Direction” of face of any triangle on part/mesh so I can make further calculations.

I described that, shoot a raycast and use the normal as I said. I explained how to get all vectors and how to get the angle.

Can I ask you about what’s `normalVector`

variable in your example is? Like, where you got it?

Also, `Vector3.new(1, 0, 0) = Vector3.xAxis`

and `Vector3.new(0, 1, 0) = Vector3.yAxis`

It’s the same as `raycastResult.Normal`

, I originally created it as a variable and removed it after but forgot to edit that part, my bad, I’ll fix the code code.

I didnt intend to sound it meanful, my bad.

So do you need a downward slope facing direction?

Perhaps this might give you an idea of how to obtain it using cross product, similar problem to sliding down a slope:

If you are orthogonalizing vectors with arbitrarily chosen direction, you won’t get the desired result in this case. You will get a vector that is parallel to the meshpart face, but that vectors is not guaranteed to have a negative y coordinate, and even if it does, it will only be the desired direction in spesific cases. The flow direction should be the most vertical downward direction (negative y with biggest possible absolute value for a unit vector parallel to the face). The method @dthecoolest suggested will give the correct vector (more spesifically a CFrame whose LookVector is the correct vector).

It doesn’t need to have negative Y-coordinate, the OP is asking for the the direction parallel, whether it’s going downwards, upwards, leftwards, etc. I’m with you in the point of flow direction being downwards though. I’m unsure who’s method is actually generally better so I’ll just wait for OP to test and decide which is better for his specific case. Not all droplets should go downwards after all.

This looks like working method, with some adjustments.

But it’s sad to see that usefull topics got markered as “Content removed”.

Sadly, I’ll need to go sleep, so I’ll test code tomorrow more, to make final decisions.

Fair point, but yes, I wanted droplets to move down for now =]

Ya of course, make sure it isn’t moving in the opposite directions though. Waiting to see the result!

This will take more time than I expected. That’s because droplets should have their velocity, resulting in situation where they can move upwards or sideways a lot with big velocity.

And main problem here comes from calculating new slope:

```
Some pseudocode (probably)
UpVector = Result.Normal OR ? Vector3.yAxis ?
LookVector = ??? OR Velocity/Vector3.zAxis (if Velocity is zero)
RightVector = ??? OR UpVector:Cross(LookVector)
```

Either I’m too stupid to understand what I need, or problem is hard and needs time.

So, I finally got some good results! But I also got 1 big problem, where droplet will go trough SECOND object if it touches more than one, and after clipping trough it, it will ignore every object it collided with. So,I now need idea how to check if there’s more than 1 tris on droplet way.

Code, so you can test this too, at least how it looks now. (not video bc of my potato pc)

```
--[==[
1. Create sphere part with name "WaterDroplet", and parent
it to workspace. ANCHOR IT!
2. Copy this script to ServerScriptService.
3. Run!
--]==]
task.wait(5) --let's allow studio load.
local RunService = game:GetService("RunService")
local Droplets = {workspace.WaterDroplet}
local SurfaceParams = RaycastParams.new()
SurfaceParams.FilterType = Enum.RaycastFilterType.Exclude
local DropMergeParams = RaycastParams.new()
DropMergeParams.FilterType = Enum.RaycastFilterType.Include
local Debounce = false
local CurDelay = 0
local WaitAmount = 0
local Gravity = 9.8
local Friction = 0.95
RunService.Heartbeat:Connect(function(Delta)
CurDelay += Delta
if Debounce == false then
Debounce = true
local i = 1
local EndValue = #Droplets
while i <= EndValue do --while loop bc I'll later reuse droplets to extinguish fire, or smth else, so they will dissapear
local Droplet = Droplets[i]
SurfaceParams.FilterDescendantsInstances = Droplets
--Checking possiblity of applying velocity
local Velocity = Droplet.AssemblyLinearVelocity - Vector3.new(0, Gravity * CurDelay, 0)
local Offset = Velocity.Unit / 100
local RayStart = CFrame.new(-Offset.X, -Offset.Y, -Offset.Z) * Droplet.CFrame
local RayDirection = Velocity--[[ * CurDelay]] + Offset
local Result = workspace:Raycast(RayStart.Position, RayDirection, SurfaceParams)
local UpVector, RightVector, LookVector
local PossibleMovement = Velocity * CurDelay
local ResultSucceed = Result and Result.Distance <= ((Velocity * CurDelay) + Offset).Magnitude and 2 or Result and 1 or 0
if ResultSucceed == 2 then
Velocity = (Velocity - Velocity:Dot(Result.Normal) * Result.Normal) * Friction
PossibleMovement = Velocity * CurDelay
end
local DropletVolume = Droplet.Size.X * Droplet.Size.Y * Droplet.Size.Z
local DropletLength = (Droplet.Size.Z + DropletVolume^(1/3) * (math.min(Velocity.Magnitude, ResultSucceed > 0 and Result.Distance or math.huge) + 1)^0.5) / 2
local DropletWidth = (DropletVolume / DropletLength)^0.5
local UpVector = ResultSucceed == 2 and Result.Normal or Vector3.yAxis
local LookVector = not (Velocity:FuzzyEq(Vector3.zero, 0.00001) or UpVector:FuzzyEq(Velocity.Unit, 0.00001)) and Velocity.Unit or not (UpVector:FuzzyEq(Vector3.zAxis, 0.00001) or UpVector:FuzzyEq(-Vector3.zAxis, 0.00001)) and Vector3.zAxis or Vector3.xAxis
local RightVector = not (UpVector:FuzzyEq(LookVector, 0.00001) or UpVector:FuzzyEq(-LookVector, 0.00001)) and (-LookVector):Cross(UpVector).Unit or Vector3.xAxis
UpVector = RightVector:Cross(LookVector).Unit
local DropletTargetPosition = Droplet.Position + PossibleMovement
Droplet.AssemblyLinearVelocity = Velocity
Droplet.CFrame = CFrame.fromMatrix(DropletTargetPosition, UpVector, RightVector, LookVector)
Droplet.Size = Vector3.new(DropletWidth, DropletWidth, DropletLength)
i += 1
end
CurDelay = 0
Debounce = false
end
end)
--[==[
If you will use this script in your games, mention me as contributor!
©GamEditoPro
--]==]
```

This’s absolutely NOT best way to make this, and it has errors (like one described above), but it’s still progress xd.

Shoot another raycast from current position to new position, if it hit something, calculate the velocity from there too and after moving the droplet for needed distance apply the new velocity, or apply both velocities at the same time.

Purpoice of that raycast? If I raycast from A to B, and then I check if smth is inbetween of them, I’ll get result that there’s nothing, because AB is already empty bc raycast was there.

I tried to make base velocity raycast, and raycast with velocity + gravity, and then find avarege normal (1n + 2n).Unit, but this failed too.

```
local RawVelocity = Droplet.AssemblyLinearVelocity
local Velocity = Droplet.AssemblyLinearVelocity - Vector3.new(0, Gravity * CurDelay, 0)
--[==[local Offset = Velocity.Unit / 100
local RayStart = CFrame.new(-Offset.X, -Offset.Y, -Offset.Z) * Droplet.CFrame
local RayDirection = Velocity--[[ * CurDelay]] + Offset
local Result = workspace:Raycast(RayStart.Position, RayDirection, SurfaceParams)]==]
local function MakeRay(Start, Velocity)
local Offset = Velocity.Unit / 100
local RayStart = CFrame.new(-Offset.X, -Offset.Y, -Offset.Z) * Start
local RayDirection = Velocity + Offset
local Result = workspace:Raycast(RayStart.Position, RayDirection, SurfaceParams)
return Result
end
local RawResult = MakeRay(Droplet.CFrame * CFrame.new(-0.001, 0, 0), RawVelocity)
local Result = MakeRay(Droplet.CFrame, Velocity)
local ResultSucceed = Result and Result.Distance <= ((Velocity * CurDelay)).Magnitude * 1.01 and 2 or Result and 1 or 0
local RawResultSucceed = RawResult and RawResult.Distance <= ((RawVelocity * CurDelay)).Magnitude * 1.01 and 2 or RawResult and 1 or 0
local Normal
if ResultSucceed == 2 then
Normal = Result.Normal
if RawResultSucceed == 2 then
Normal = (Normal + RawResult.Normal).Unit
end
Velocity = (Velocity - Velocity:Dot(Normal) * Normal) * Friction
end
local PossibleMovement = Velocity * CurDelay
```

No, I meant after the raycast from A to B u shoot another raycast from B to C, the *new* position (target position).

Don’t understood. I have start, A and velocity, B. I cast from start in velocity direction. Got AB. Got result position, B. What are C?