Need help with Tiling an my current ocean patch

I want to create an infinite tiling ocean. So far this is my code:

-- Get the required services
local AssetService = game:GetService("AssetService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

-- Constants
local g = 35         -- Gravitational constant (in m/s^2)
local offset = 0.5          -- Spacing between vertices
local amplitude = 0.08     -- Amplitude A of the wave

-- Animation loop time (Section 4.2 implementation)
local T = 10 -- Loop time in seconds
local omega0 = (2 * math.pi) / T

-- Function to generate a random wave (optional)
local function generateRandomWave()
	local direction = Vector3.new(math.random() - 0.5, math.random() - 0.5, 0).Unit
	local wavelength = math.random(20, 60)  -- Wavelength (lambda)
	local k = (2 * math.pi) / wavelength	
	local omega_unadjusted = math.sqrt(g * k) * 2
	local omega = math.floor((omega_unadjusted / omega0) + 0.5) * omega0 -- Quantized omega

	return {
		direction = direction,
		wavelength = wavelength,
		k = k,
		omega = omega
	}
end

-- Define waves and precompute k and adjusted omega
local waves = {
	{ direction = Vector3.new(1, 0, 0), wavelength = 60 },   -- Wave A
	{ direction = Vector3.new(0.7, 0.4, 0), wavelength = 45 }, -- Wave B
	{ direction = Vector3.new(0.3, 0.9, 0), wavelength = 30 }, -- Wave C
	{ direction = Vector3.new(0.9, 0.1, 0), wavelength = 20 }, -- Wave D
}

for i = 1, 10 do
	table.insert(waves, generateRandomWave())
end

-- Precompute k and adjusted omega for each wave
for _, wave in ipairs(waves) do
	wave.wavelength = wave.wavelength / 4
	wave.k = (2 * math.pi) / wave.wavelength
	local omega_unadjusted = math.sqrt(g * wave.k) * 2
	wave.omega = math.floor((omega_unadjusted / omega0) + 0.5) * omega0 -- Quantized omega
end

-- Gerstner wave function (with adjusted omega)
local function GerstnerWave(wave, vertPos, currentTime)
	local direction = wave.direction.Unit
	local k = wave.k
	local omega = wave.omega

	-- Calculate the phase: k ⋅ x - ωt
	local phase = k * (direction.X * vertPos.X + direction.Y * vertPos.Z) - (omega * currentTime * 0.3)

	-- Calculate displacements (textbook parametric equations)
	local horizontalDisplacement = Vector3.new(
		-amplitude * math.sin(phase) * direction.X,
		0,
		-amplitude * math.sin(phase) * direction.Y
	)
	local verticalDisplacement = amplitude * math.cos(phase)

	return Vector3.new(
		horizontalDisplacement.X,
		verticalDisplacement,
		horizontalDisplacement.Z
	)
end

-- Mesh setup
local mesh = game.Workspace:WaitForChild("Ocean")
local editableMesh = Instance.new("EditableMesh", mesh)
local width, height = 100, 100
local vertices = {}

-- Create a grid of vertices
for y = 1, height do
	local row = {}
	for x = 1, width do
		local vertPos = Vector3.new(x - 1, 0, y - 1) * offset
		local vertID = editableMesh:AddVertex(vertPos)
		row[x] = { vertID = vertID, vertPos = vertPos }
	end
	vertices[y] = row
end

-- Create triangles between vertices to form the mesh surface
for y = 1, height - 1 do
	for x = 1, width - 1 do
		local v1 = vertices[y][x].vertID
		local v2 = vertices[y + 1][x].vertID
		local v3 = vertices[y][x + 1].vertID
		local v4 = vertices[y + 1][x + 1].vertID
		editableMesh:AddTriangle(v1, v2, v3)
		editableMesh:AddTriangle(v2, v4, v3)
	end
end

-- Get the player's camera
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

-- Update the mesh on each frame
RunService.Heartbeat:Connect(function()
	local currentTime = os.clock()
	for y = 1, height do
		for x = 1, width do
			local vertData = vertices[y][x]
			local vertID = vertData.vertID
			local vertPos = vertData.vertPos

			-- Convert vertex position to world space
			local worldVertPos = mesh.CFrame:PointToWorldSpace(vertPos)

			-- Check if the vertex is within the camera's view
			local screenPoint, onScreen = camera:WorldToViewportPoint(worldVertPos)

			if onScreen then
				-- Sum displacements from all waves
				local totalDisplacement = Vector3.new(0, 0, 0)
				for _, wave in ipairs(waves) do
					totalDisplacement += GerstnerWave(wave, vertPos, currentTime)
				end

				-- Calculate new vertex position
				local newPos = vertPos + totalDisplacement

				-- Update vertex position in EditableMesh
				editableMesh:SetPosition(vertID, newPos)
			else
				-- Optionally reset to original position
				editableMesh:SetPosition(vertID, vertPos)
			end
		end
	end
end)

The code above produces the following patch of ocean

How would I go about tiling this? It’s quite performance heavy already, so any optimisations to improve this would be nice.

Thanks!

2 Likes

Why tile them?
Use 1 large skinned mesh ocean instead.
Search “mesh ocean” in the forums for posts about them.

1 Like

it really depends on how your trying to use them. do you want them to generate around the player or do you want it to be all at once everywhere?

decreases the quality drastically. remember the 10,000 triangle limit. id rather have multiple high quality ocean tiles surrounding the player, obviously with an LOD system to reduce lag rather than a giant single ocean tile that looks ugly.

generate around the player, with an LOD system to reduce total triangle count. I want a high quality ocean but obviously too many triangles will destroy most devices.