How I can get angle between vector and mesh's face? (Or directional vector of mesh's face)

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:
image
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?

6 Likes

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)
5 Likes

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!

3 Likes

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.

5 Likes

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.

3 Likes

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.

3 Likes

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

1 Like

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.

3 Likes

I didnt intend to sound it meanful, my bad.

2 Likes

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:

3 Likes

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).

4 Likes

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.

2 Likes

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 =]

2 Likes

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

3 Likes

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.

2 Likes

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.

1 Like

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.

1 Like

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?