Realistic Oceans Using Mesh Deformation!

Blender isn’t totally my thing, so I’m not sure if I would be of much help.

Check out this tutorial here and if you still have the problem, try asking it there.

1 Like

Question, very late

Have you gotten around to make this work completely or not? I´m looking for a solution to bone collission within oceans/seas which are animated

That will be very difficult to do, as Roblox has not added support for collision detection with skinned meshes.

Collision detection isn’t necessarily needed, it can be calculated without it

Theres multiple videos on it just look up “gerstner waves roblox”

Take the object you’d want as a sort of “boyant/boyancy block”

Great tutorial imo, thanks a ton!

2 Likes

I did some research thru APIs and the ¨Skinned MeshParts are live!¨ post and came across this API:

¨ TransformedWorldCFrame¨
Describes the combined CFrame offset of the bone and the current animation offset in world space.

This could help with collision, possibly

Another way I´ve seen before is create triangles on the gerstner waves, which follow the same wave path to detect collision way easier (but more performance impact probably)

Along with that I came across this example:

This is probably the closest we can get to collision detection for skinned meshes, for now.

That’s some good research right there. However, unless there is an official implementation, I don’t plan on updating the module with collisions. I don’t believe it will exactly be performant in real situations, but it’s always worth a shot!

1 Like

Is it possible to detect if a player/camera is inside the water?

1 Like

@iamtryingtofindname
Is there any way to condense your expansive and useful module into a single function that takes a vector 3 and returns where that vector 3 should be at that returned time?

-- call a function and pass the origin point before the translation (and perhaps the settings?)
Wave.GerstnerPoint(Vector3)
-- expected return is the vector3 post-translation 
x, y, z

This has to be possible and simple (but math and coding aren’t my areas of expertise)
^ Any arbitrary bone can be translated by the gerstner function you have, as long as it exists in the workspace, therefore any arbitrary point should have its own translation. This is obvious, I think.

I tried a couple methods and all failed, however, the method that made the most headway was:
Putting all necessary functions in a serverscript for the individual floating part
Taking the part’s position as the vector3 to be translated
Placing the translated vector3 as the bodyposition’s position

The one problem I faced was that I could not correctly isolate and carry over your perlin noise function.

Yeah sure I can add that real quick

1 Like

Lemme know if it works. Download the updated module:

Source:

-- Added per request of @Desired_Knowledge
function Wave:GerstnerPoint(point: Vector3)
	local WorldPos = point
	local Settings = self._settings
	local Direction = Settings.Direction

	if Direction == EmptyVector2 then
		-- Use Perlin Noise
		Direction = Vector2.new(math_noise(WorldPos.X/NoiseModifier,WorldPos.Z/NoiseModifier,1),math_noise(WorldPos.X/NoiseModifier,WorldPos.Z/NoiseModifier,0))
	else
		Direction = GetDirection(Settings,WorldPos)
	end
	
	local transform = newCFrame(Gerstner(WorldPos,Settings.WaveLength,Direction,Settings.Steepness,Settings.Gravity,self._time))
	
	return transform, point+transform
end

Just realized I never hit reply lol

1 Like

It works properly, but its not synced with the waves. It doesn’t seem to be the same wave at all.

Now, I did make edits to the module to have the plane anchor to the HumanoidRootPart and keep the points where they should be, had the plane never moved. I move the plane, and subtract the humanoidrootpart’s x/z position from the worldposition of the bones. In this way, the water is basically infinite. There must be some issue in the way I did that though.

I’m trying to have an infinite ocean that uses only 1 plane and also has waves that are pretty synced from client to server to clients. I asked about the point method so I could add swimming and floating physics, as well as a check for if your camera is under water.

If that’s an edit you can walk me through, or make altogether (as long as it isn’t asking too much), that would be extremely appreciated and helpful. I can commission you, otherwise.

The problem with Gerstner waves is that there is no mathematically correct way to get the waveheight at a given xz-position.

The best you can do is use approximations. What I found by researching the problem is that you can achieve a pretty good result by plugging the wave formula in twice. As in you first calculate one offset of the position, then pass that new position in the formula again to extract the height.

How would I do that using these above methods?

Something like this:

function getHeight(XZPos)
	local w = computeDisplacement(XZPos)
	local correctedXZPos = Vector2.new(XZPos.X + w.X, XZPos.Y + w.Z)
	return computeDisplacement(correctedXZPos).Y
end

computeDisplacement() is a function that takes in an XZ-coordinate and returns the offset on that point (Vector3).

I got some version of it working, but my issue now is that the points used are the object’s origin points. That means I can’t have the items move around the x/z plane outside of the gerstner’s movement. If I allow them to move, then they aren’t in sync with the wave plane anymore. That’s the issue with swimming, camera underwater check, and boat floating.

This seems like a weird issue.

The Gerstner formula should work for any point up to infinity. Are you referencing the bones somehow? Could you show the relevant code pieces?

Well, I wasn’t entirely clear in my explanation. The objects float with a gerstner wave pattern and it roughly matches the waves (this could be an issue with my client server synchrony method).

These objects are stationary and are not dynamic. Think of them like buoys that just bob around their origin like bones would.

My problem arises when I have dynamic objects (that don’t really follow the x/z aspect of the gerstner). Say, for instance:

  • a barrel that is pushed away from that origin point
  • a character that does not originate from an origin point on the water and swims freely
  • the player’s character when going under the y height of the wave at its present x/z position.

As for the code sample, I actually deleted what I had for the floating objects. It was practically the gerstner function for the bones with some changes so it would put the return in a bodyposition.


Gerstner formula function

function GerstnerWave(SamplePosition,Wavelength,Direction,Steepness,Gravity,SampleTick)
	local k = (2 * math.pi) / Wavelength
	local a = Steepness/k
	local d = Direction.Unit
	local c = math.sqrt(Gravity / k)
	local f = k * d:Dot(Vector2.new(SamplePosition.X,SamplePosition.Z)) - c * SampleTick
	local cosF = math.cos(f)

	--Displacement Vectors
	local dX = (d.X * (a * cosF))
	local dY = a * math.sin(f)
	local dZ = ( d.Y * (a * cosF))
	return Vector3.new(dX,dY,dZ)
end

point transform (takes a vector3 argument)

function gerstnertransform(v)
	local WorldPos = v
	local Wave1 = GerstnerWave(WorldPos,100,Vector2.new(.5,0),.14,3,SyncModule:Time()*timemodifier*1)
	local Wave2 = GerstnerWave(WorldPos,120,Vector2.new(.8,.2),.07,15,SyncModule:Time()*timemodifier*0.6)
	local Wave3 = GerstnerWave(WorldPos,300,Vector2.new(.7,1),.04,15,SyncModule:Time()*timemodifier*0.3)
	local TotalDisplacement = Wave1+Wave2+Wave3
	return TotalDisplacement
end

To make that work, you will need to call your gerstnertransform() function like my computeDisplacement() function. (see my previous reply)

You only need to return the height.

1 Like

I don’t think I’m implementing that function correctly at all. I don’t know what I’m supposed to do with it.


These are the functions:

function GerstnerWave(SamplePosition,Wavelength,Direction,Steepness,Gravity,SampleTick)
	local k = (2 * math.pi) / Wavelength
	local a = Steepness/k
	local d = Direction.Unit
	local c = math.sqrt(Gravity / k)
	local f = k * d:Dot(Vector2.new(SamplePosition.X,SamplePosition.Z)) - c * SampleTick
	local cosF = math.cos(f)

	--Displacement Vectors
	local dX = (d.X * (a * cosF))
	local dY = a * math.sin(f)
	local dZ = ( d.Y * (a * cosF))
	return Vector3.new(dX,dY,dZ)
end

function wavefunction(v) -- This is used for the bones, and it works correctly
	local WorldPos = v.WorldPosition - Vector3.new(root.Position.X,0,root.Position.Y)
	local Wave1 = GerstnerWave(WorldPos,90,Vector2.new(.5,.5),.4,4,SyncModule:Time()*timemodifier*1.2)
	local Wave2 = GerstnerWave(WorldPos,120,Vector2.new(.8,.2),.1,15,SyncModule:Time()*timemodifier*1)
	local Wave3 = GerstnerWave(WorldPos,400,Vector2.new(.7,0),.015,1000,SyncModule:Time()*timemodifier*0.02)
	local Wave4 = GerstnerWave(WorldPos,400,Vector2.new(0,1),.01,1000,SyncModule:Time()*timemodifier*0.026)
	local TotalDisplacement = Wave1+Wave2+Wave3+Wave4
	v.Transform = CFrame.new(TotalDisplacement)
end

function gerstnertransform(v)
	local WorldPos = v
	local Wave1 = GerstnerWave(WorldPos,90,Vector2.new(.5,.5),.4,4,SyncModule:Time()*timemodifier*1.2)
	local Wave2 = GerstnerWave(WorldPos,120,Vector2.new(.8,.2),.1,15,SyncModule:Time()*timemodifier*1)
	local Wave3 = GerstnerWave(WorldPos,300,Vector2.new(.7,0),.015,1000,SyncModule:Time()*timemodifier*0.02)
	local Wave4 = GerstnerWave(WorldPos,300,Vector2.new(0,1),.01,1000,SyncModule:Time()*timemodifier*0.026)
	local TotalDisplacement = Wave1+Wave2+Wave3+Wave4
	return Vector2.new(TotalDisplacement.X,TotalDisplacement.Z)
end

Here is the swimming height offset detector

rs:Connect(function()
	plane.Caustics.OffsetStudsU = -root.Position.X * 0.75
	plane.Caustics.OffsetStudsV = -root.Position.Z * 0.75
	plane.WaterTexture.OffsetStudsU = -root.Position.X * 1.2
	plane.WaterTexture.OffsetStudsV = -root.Position.Z * 1.2
	
	plane.Position = Vector3.new(root.Position.X,plane.Position.Y,root.Position.Z)
	
	for _,v in pairs(bones) do -- this whole loop works the way its expected to, albeit its really performance heavy.
		wavefunction(v)
	end
	
	if root.Position.Y < getHeight(gerstnertransform(Vector3.new(root.Position.X,plane.Position.Y,root.Position.Z))) then
		print('underwater')
	end
	
end)

First of all, without editing it, there’s an error because Vector3 and Vector2 are not the same, and I don’t know how the two vectors are supposed to relate when it comes to the formulae you guys provided.
Secondly, I don’t really know if I’m implementing it correctly – I’m 99.9% certain that I’m extremely off.
Edit: Thirdly, it seems like I’m not even anchoring the plane to my character correctly about half the time. My movement displaces the waves sometimes.

1 Like