Making A Mesh Deformation Ocean

its the same equation and don’t use the cframe use the position

What do you mean you’re solving for the entire position?
https://gyazo.com/e84c56f898332d8240a6480f10298c29

I’m still getting these results while using a bodyPos with the X being the position’s X.

function sin(a, b, c, d, e, f)
    local x = a * math.sin(b * c + (d * e)) + f
    return x
end

if root ~= nil and root:IsDescendantOf(player.Character) then
	bp.Position = Vector3.new(0, sin(amplitude, root.Position.X, frequency, syncedTime + increment, speed, height), 0)
	bp.Parent = root
	root.Parent.Humanoid.WalkSpeed = 32
end

Its because they are trying to help people who aren’t as talented as you at converting ideas to scripts.

4 Likes

How’d you make the server to client syncronizer, can’t seem to get to understand it.

Thank you so much for this quality post! I already got a skinned mesh sea working, but I was really struggling with implementing Gerstner waves.

2 Likes

Thanks, this really helped me out with understanding skinned meshes! I have 0 experience with Blender, so I was quite glad to have found this post.

1 Like

What an amazing feat! Well done, I will use this for future projects.

1 Like

Is there any way to randomize the waves? I have tried using math.noise and math.random but they both break the mesh.

2 Likes

Sorry for a little bump, but with like 1 small correction I got this:


local plane = workspace:WaitForChild("Ocean"):WaitForChild("Plane")

local origPosTable = {}
local LocalPlayer = game.Players.LocalPlayer
local speedCounter = 20
local maxDistance = 10


for _, bone in pairs(plane:GetChildren()) do
	if bone:IsA("Bone") then
		table.insert(origPosTable,bone.Position)
		origPosTable[bone] = bone.Position
	end
end

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

game:GetService("RunService").Heartbeat:Connect(function()
	for _, bone in pairs(plane:GetChildren()) do
		if bone:IsA("Bone") then
			if origPosTable[bone] 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)

localscript in startergui, it works but it freezes, so it only goes for like a millisecond and then it just stops without any errors nor output

1 Like

Update: Got this working

Now I need some way to make objects and characters interact with the waves, so they ¨collide¨

You are correct! i did ‘stumble’ upon your post; it is timely, excellent information; as i am just learning blender this provides yet another project.
Thank You!

To be honest, I’d avoid body movers if you intend to keep original character functionality. When you start messing with BMs then you lose some core mechanics. What I do instead is create a small platform underneath the player that positions itself underneath the character, set the plane to not collide with the character then calculate the GerstnerWave, get the Y and adjust the platform to the Y. The result is the player remains on the platform due to the Hipheight popping the character up while maintaining its state.

https://gyazo.com/f10f1bcd1e51f965f561aef0ef7fb81a

Using the Ocean.rbxl provided in the post, forking it and adding additional code at the bottom, we effectively have a platform that simulates staying afloat.

-- Setup wave movement connection
local skipCounter = 0
local speedCounter = 0
game:GetService("RunService").Heartbeat:Connect(function()
	skipCounter += 1
	speedCounter += speedMultiple
	if skipCounter >= 2 then
		skipCounter = 0
		
		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;
			
			workspace.EEERRR.Position = LocalPlayer.Character:GetPrimaryPartCFrame() 
				* CFrame.new(0, -LocalPlayer.Character.Humanoid.HipHeight/2, 0).Position;
			
			local BoxSamplePosition = workspace.EEERRR.Position;
			local Wave1 = GerstnerWave(BoxSamplePosition, 150, Vector2.new(1, 0), .05, 1, speedCounter)
			local Wave2 = GerstnerWave(BoxSamplePosition, 175, Vector2.new(0, .3), .2, 1.25, speedCounter)
			local Wave3 = GerstnerWave(BoxSamplePosition, 200, Vector2.new(1, 1), .1, 1.5, speedCounter)
			local TotalDisplacement = Wave1 + Wave2 + Wave3
				
			workspace.EEERRR.Position  = Vector3.new(
				BoxSamplePosition.X, 
				TotalDisplacement.Y + workspace.EEERRR.Size.Y/2, 
				BoxSamplePosition.Z
			);
		end;
	end
end)

Of course, you’ll want to clean it up, however, and make it work for you. I haven’t tested it going from land to the deformation water. Guess you can do some raycasting to see if you’re hitting the plane or not and have the platform gradually push you up to water level? (I did this in like 7 minutes so happy problem solving)

7 Likes

Hey! Thanks for this, but when I playtest the whole plane actually moves away from where its supposed to be. What could be the reason and how am I supposed to fix this? Thanks. Here’s my code:

local origPosTable = {}

for i, bone in pairs(workspace.Ocean.Plane:GetChildren()) do
	if bone:IsA("Bone") then
		origPosTable[bone] = bone.Position
	end
end

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

game:GetService("RunService").Heartbeat:Connect(function()
	for i, bone in pairs(workspace.Ocean.Plane:GetChildren()) do
		if bone:IsA("Bone") then
			local bonePos = GerstnerWave(origPosTable[bone], 90, Vector2.new(1, 0), .2, 1.5, tick())
			bone.WorldPosition = origPosTable[bone] + bonePos
		end
	end
end)
2 Likes

Oh nvm, I had to set bone.Position and not bone.WorldPosition. For anybody stumbling upon this in the future make sure you’re setting the bone’s position and not world position! Have a good day!

2 Likes

Prety well written for a first tutorial… Thanks for this! I have been trying to figure out how to import such meshes into Roblox to no avail, and the formula is pretty decent if you ask me. I am playing with some tweaks to make it look a tad smoother but overall, I am so happy to have found your post!
Thanks!

1 Like

Enabling use_normals causes horrible results for me (I’m trying to subdivide a cylinder):

Any idea what might be going on?

EDIT: Was able to fix it. Code I used below:

objects = bpy.context.view_layer.objects
obj = objects.active
def AddBonesAtVertices(obj, length, use_normals):
    if not obj or obj.type != 'MESH':
        return
    points = []
    normals = []
    data = []
    for v in obj.data.vertices:
        p = obj.matrix_world @ v.co
        dir = p * Vector((1, 0, 1))
        dir.normalize()
        n = p + dir * length
        points.append(p)
        if not use_normals:
            n = Vector((p[0], p[1], p[2] + length))
        normals.append(n)
        data.append([p, n])
    bpy.ops.object.mode_set(mode='OBJECT')
    amt = bpy.data.armatures.new(obj.name + "_vBones")
    rig = bpy.data.objects.new(obj.name + '_vRig', amt)
    bpy.context.collection.objects.link(rig)
    objects.active = rig
    names = [] #Will keep bone names
    bpy.ops.object.editmode_toggle()
    for i, l in enumerate(zip(points, normals)):
        bone = amt.edit_bones.new(str(i))
        bone.head = l[0]
        bone.tail = l[1]
        names.append(bone.name) #Add name
    bpy.ops.object.editmode_toggle()
    for v_index, name in enumerate(names):
        #Get the group
        group = obj.vertex_groups.new(name=name)
        #Link the vertex to it
        group.add([v_index], 1, 'REPLACE')
    #Parent and add modifier
    obj.parent = rig
    modifier = obj.modifiers.new(rig.name, "ARMATURE")
    modifier.object = rig

AddBonesAtVertices(obj, 0.5, True)

The result:

2 Likes

Is there a way to prevent the mesh from clipping through objects as an example boats.

1 Like

Is there anyway to make the tile smaller? I set the size to be 500x500, but it stills looks 2000x2000? I needa be able to tile it together, but have gaps for underground areas to work

Sorry for huge bump, but it’s not working as intended :confused:

for _, bone in plane:GetChildren() do
		if not bone:IsA("Bone") then
			continue
		end

		if not self.OriginalPositions[bone] then
			continue
		end

		if Player:DistanceFromCharacter(bone.WorldPosition) > MAX_DISTANCE then
			continue
		end

		local SamplePosition = bone.WorldPosition
		local Wave1 = GerstnerWave(SamplePosition, 150, Vector2.new(1, 0), 0.025, 1, self.SpeedCounter[plane])
		local Wave2 = GerstnerWave(SamplePosition, 175, Vector2.new(0, 0.3), 0.08, 1.25, self.SpeedCounter[plane])
		local Wave3 = GerstnerWave(SamplePosition, 200, Vector2.new(1, 1), 0.04, 1.5, self.SpeedCounter[plane])

		local TotalDisplacement = Wave1 + Wave2 + Wave3

		bone.Position = self.OriginalPositions[bone] + TotalDisplacement
	end

	local BoxSamplePosition = Character:GetPivot().Position - Vector3.new(0, Character.Humanoid.HipHeight / 2, 0)
	local Wave1 = GerstnerWave(BoxSamplePosition, 150, Vector2.new(1, 0), 0.025, 1, self.SpeedCounter[plane])
	local Wave2 = GerstnerWave(BoxSamplePosition, 175, Vector2.new(0, 0.3), 0.08, 1.25, self.SpeedCounter[plane])
	local Wave3 = GerstnerWave(BoxSamplePosition, 200, Vector2.new(1, 1), 0.04, 1.5, self.SpeedCounter[plane])

	local TotalDisplacement = Wave1 + Wave2 + Wave3

	workspace.EEERRR.Position =
		Vector3.new(BoxSamplePosition.X, 13 + TotalDisplacement.Y - workspace.EEERRR.Size.Y / 2, BoxSamplePosition.Z)

ezgif.com-gif-maker - 2022-12-14T122135.697

3 Likes

Sorry for bump, but can you give more detail on how to do this? (Sorry I am new to blender)

The script gives error when ran (note: I am using blender 2.79 because new version isn’t supported on my pc)