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

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?

So, after some deeper research and tries to understand what’s wrong, I understood what I did wrong and what you was telling me:
image
So, in some cases, droplet can be too close to wall with my old check so it will detect only blocking wall, ignoring lower one. (Previous check checked base velocity and velocity + gravity. So it was worst method I had ever tbh, bc limit of this was 2 walls)
So, I rescripted detection method a bit:

local Droplet = Droplets[i]
local StartVelocity = Droplet.Velocity
SurfaceParams.FilterDescendantsInstances = Droplets
local Normals = {}
local function TestVelocity(Start, Velocity)
	local VelocityGrav = Velocity - Vector3.new(0, Gravity * CurDelay, 0) 
	local Offset = VelocityGrav.Unit / 20
	local RayStart = -Offset + Start
	local RayDirection = VelocityGrav + Offset
	local Result = workspace:Raycast(RayStart, RayDirection, SurfaceParams)
	local RaycastDistanceTarget = (VelocityGrav.Magnitude + Offset.Magnitude) * CurDelay + 0.05
	local ResultSucceed = Result and Result.Distance <= RaycastDistanceTarget and 2 or Result and 1 or 0
	local Distance = math.huge
	if ResultSucceed == 2 then
		if #Normals > 100 then
			error("TOO MANY NORMALS")
		else
			local found = false
			for i = 1, #Normals, 1 do
				if Normals[i]:FuzzyEq(Result.Normal, 0.001) then
					found = true
					break
				end
			end
			if not found then
				table.insert(Normals, Result.Normal)
				local Normal = Normals[1]
				for i = 2, #Normals, 1 do
					Normal += Normals[i]
				end
				Distance = Result.Distance
				Normal = Normal.Unit
				local NextVelocity = Velocity - Velocity:Dot(Normal) * Normal
				local PreDistance = TestVelocity(Start, NextVelocity)
				Distance = PreDistance ~= math.huge and PreDistance or Distance
			end
		end
	end
	return Distance
end

local Distance = TestVelocity(Droplet.Position, StartVelocity)
local Velocity = StartVelocity - Vector3.new(0, Gravity * CurDelay, 0)
local PossibleMovement = Velocity * CurDelay
local Normal = Normals[1]
if #Normals > 0 then
	for i = 2, #Normals, 1 do
		Normal += Normals[i]
	end
	Normal = Normal.Unit
	local NewVelocity = (Velocity - Velocity:Dot(Normal) * Normal)-- * Friction
	local DistanceProportion = math.min(Distance / NewVelocity.Magnitude, 1) * 0.99
	Velocity = (Velocity * (0.99 - DistanceProportion) + NewVelocity * (0.01 + DistanceProportion)) * Friction
	PossibleMovement = Velocity * CurDelay
	Velocity = NewVelocity * Friction
end

And also I found small bug which goes in action when you detect surface. So, there’s previous and reflection velocities. If surface detected, 100% power will go to reflect velocity. On big speeds this can result in hitting mid-air. And I tried to fix this one too. But failed - again due to clipping problems.

I’m pretty sure decreasing offset (by, for example, diving by 30 instead of 20) will fix that, it’ll decrease distance needed for raycast to count as raycast so making this mid-air movements be less noticeable.

I already had 1/100. Result is ± same. I think that problem may come from Distance / Velocity.Magnitude, but I’m not sure.

Pretty weird, can you explain why you add to CurDelay every frame? That increases the distance too.