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?
So, after some deeper research and tries to understand what’s wrong, I understood what I did wrong and what you was telling me:
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.