# Making A Mesh Deformation Ocean

I considered writing my own version of a guide like this, but instead, I’ll just post a few tips:

1. If you are using textures rather than a SurfaceAppearance, you can lock the water plane to the position of the camera and make it appear to move properly by scrolling the UV offset of the texture.

2. Use the same formula that you used to generate the water to detect surface collisions by sampling the wave at the target position rather than trying to detect collisions with the actual wave mesh.

Here is a Gerstner wave function you can use to generate your waves:

``````--GerstnerWave(Vector3 SamplePosition, Float Wavelength, Vector2 Direction, Float Steepness, Float Gravity, Float SampleTick)
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
``````
1. The wavelength of your smallest wave must be larger than the distance between vertices on your water mesh, otherwise, it will look pretty strange.

2. You can add the displacement from multiple Gerstner waves to get a more convincing final displacement for the water.

21 Likes

OMG IVE BEEN LOOKING FOR SOMETHING LIKE THIS FOR SO LONG!!! Yay

1 Like

I can’t tell you how much this is amazing timing for me. Due to that being a script imported from blender, would it possible for this to become a model?

2 Likes

Yeah, I could make the ocean mesh a model and link it in the original post.

2 Likes

Hi, I’m having difficulties trying to get this function to work. Could you provide a code snippet, or explain what type each variable has to be?
Thank you.

2 Likes

GerstnerWave(Vector3 SamplePosition, Float Wavelength, Vector2 Direction, Float Steepness, Float Gravity, Float SampleTick)

SamplePosition is the position at which you are checking the wave displacement (E.G. the world position of a given bone in a water mesh)

Wavelength, Direction, Steepness, and Gravity are all pretty self-explanatory, but it does take some tinkering to find numbers that look nice.

SampleTick is the time you are sampling the wave at, should be something that you can sync between clients if possible. Waves sampled at the same SampleTick with the same arguments will always return the same results (deterministic).

Here are some example arguments that I’ve found look pretty nice:

``````	local Wave1 = GerstnerWave(SamplePosition,80,Vector2.new(1,0),.05,1.5,SampleTick)
local Wave2 = GerstnerWave(SamplePosition,90,Vector2.new(0,.3),.07,1.5,SampleTick)
local Wave3 = GerstnerWave(SamplePosition,100,Vector2.new(1,1),.05,1.5,SampleTick)
local TotalDisplacement = Wave1+Wave2+Wave3 -- This is your final wave displacement
``````
1 Like

Hello again, I am struggling to to import the FBX into Studio. Would you mind exporting this as a Roblox model?

Thanks

3 Likes

Wow, this can help alot I appreciate it!

Thank you for the clarification.
I’m currently doing this inside of a loop to update all the bones.

``````for _, bone in pairs(plane:GetChildren()) do
if bone:IsA("Bone") then
local SamplePosition = bone.Position
local SampleTick = tick()

local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
local TotalDisplacement = Wave1 + Wave2 + Wave3 -- This is your final wave displacement
bone.Position = TotalDisplacement
end
end
``````

It doesn’t work however. Is there another way to set a bone’s position?

You’ll want to sample the wave at the WorldPosition of the bone instead of the Position. That could be the cause of your issue.

1 Like

Something like this?

``````for _, bone in pairs(plane:GetChildren()) do
if bone:IsA("Bone") then
local SamplePosition = bone.WorldPosition
local SampleTick = tick()

local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
local TotalDisplacement = Wave1 + Wave2 + Wave3
bone.Position = TotalDisplacement
end
end
``````

This doesn’t seem to work either…

You also need to cache the initial position of the bone, the GerstnerWave function is returning a displacement that is meant to be added to a position.

Something like:

``````bone.Position = OriginalPosition+TotalDisplacement
``````
1 Like

just to clarify, would this be the final product?

``````for _, bone in pairs(plane:GetChildren()) do
if bone:IsA("Bone") then
local SamplePosition = bone.WorldPosition
local SampleTick = tick()

local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
local TotalDisplacement = Wave1 + Wave2 + Wave3
local OriginalPosition = bone.Position
bone.Position = OriginalPosition+TotalDisplacement
end
end
``````

No, if you do that you are changing the OriginalPosition of each bone each step. You’ll want to store (cache) the original position of each bone in a table at the start and get it from there. You could probably also use Bone.Transform, but I haven’t experimented with that yet.

what do you mean by this? and how would I acheive it?

1 Like

Here’s how I do it:

``````game:GetService("RunService").Heartbeat:Connect(function()
local char = LocalPlayer.Character
if char and char:FindFirstChild("HumanoidRootPart") then
for _, bone in pairs(plane:GetChildren()) do
if bone:IsA("Bone") then
if origPosTable[bone] then
if (char.HumanoidRootPart.Position - bone.WorldPosition).Magnitude <= maxDistance then
local SamplePosition = bone.WorldPosition

local Wave1 = GerstnerWave(SamplePosition, 150, Vector2.new(1, 0), .05, 1, speedCounter)
local Wave2 = GerstnerWave(SamplePosition, 175, Vector2.new(0, .3), .2, 1.25, speedCounter)
local Wave3 = GerstnerWave(SamplePosition, 200, Vector2.new(1, 1), .1, 1.5, speedCounter)
local TotalDisplacement = Wave1 + Wave2 + Wave3
bone.Position = origPosTable[bone] + TotalDisplacement
end
end
end
end
end
end)
``````

The origPosTable contains every bone’s position. This is initialized at the start of the script.

2 Likes

is this a local script? I see localplayer… so

the char and humanoid root part is not necessary in the script?

Yes, I believe it is. LocalPlayer can only be used on the client.