Deformed Gerstner Waves

Greetings,

For the past few days I’ve been trying to make a custom water system for my game, starting with Gerstner Waves. My code stems from this tutorial however the code is pretty much the same (as in it’s been mirrored to Roblox). Upon testing today I found that the waves would stretch in very peculiar axis:

It reflects the correct behaviour however it is on the wrong axis and to put it bluntly I’m stumped. I’ve tried alternating between compound operators but to no avail.

Code
local runService = game:GetService("RunService")

local configuration = script.Configuration

local sizeX = configuration.SizeX.Value
local sizeY = configuration.SizeY.Value
local resolutionConfig = configuration.Resolution.Value
local positionConfig = configuration.Position.Value

local amplitude = configuration.Amplitude.Value
local waveLength = configuration.WaveLength.Value
local speed = configuration.Speed.Value

local gridCreator = require(script.GridCreator)

local grid = gridCreator.new({
	Size = Vector3.new(sizeX, 5, sizeY),
	Position = positionConfig,
	
	Resolution = resolutionConfig,
	
	DrawType = "WedgePart"
})

while wait() do
	for x = 1, grid.Resolution do
		for y = 1, grid.Resolution do
			local wave = 2 * math.pi / waveLength
			local formula = wave * (y - speed * os.clock())
			local tangent = grid.Position:Dot(Vector3.new(
					1 - wave * amplitude * math.sin(formula),
					wave * amplitude * math.cos(formula), 
					0
				)
			)
			
			local normal = Vector3.new(-tangent, tangent, 0)
			
			grid.Points[x][y].X += math.clamp(amplitude * math.cos(normal.x), 0, math.pi * 2)
			grid.Points[x][y].Y = math.clamp(amplitude * math.sin(normal.y), 0, math.pi * 2)
		end
	end
end

(this uses boatbombers fantastic gridcreator module)

I’d be extremely grateful for any help at the moment, thank you in advance.

2 Likes

It doesn’t have anything to do with one of these being += and the other just being = does it?

1 Like

Nope, it was += in the original tutorial however if I set it to something like = I just get the following result:

image

1 Like

I’m not sure exactly what your goal is for the grid.Points structure. Is it just to hold all the position data for the waves?

I’m not particularly good at explaining things so you can refer to boatbombers post for that sort of info:

1 Like

Thanks, yeah just had a read of that as I realise you put in the bottom of your post what that grid thing was, my bad.

I don’t think you have to mess around at all with tangents / normals from what I’ve seen. You basically just need a height map which is the amplitude of the wave itself.

The maths basically is this:

Wave-vector, k defines the direction of travel of the wave. In this case I think it’s just Vector2.new(1,0) (travel in the x direction).
The amplitude is then given by:

A( r, t) = A_0 sin( k :Dot( r) - w * t )

r is the 2d position ( x and z coordinates of the grid position), A_0 is the amplitude of the wave in studs(?). w is the angular frequency which you can get from the wave speed by doing w = c |k| .

Let me know if you need any more explanation or help implementing this.

Do you mind elaborating on what you mean? I’m more of a visual learner so formatting your maths would be a great help

(sorry if i’m being a pain)

1 Like

Yeah, happily. I’ll explain more about each of the terms I’ve mentioned. I’m mainly just going to stick the with standard maths / physics variable names to make it easier to compare other sources of information on wave mechanics.

Parameter Explanations

k, the wavevector defines the direction in which the wave will move. In your code you can write local k = Vector2.new(0, 1) ( or any other components you like). The magnitude of the wavevector is inversely proportional to the wavelength of the wave. A “longer” k vector leads to more “bunched up” waves.

ω, the angular frequency, is how frequently each point moves up and down, measured in radians per second.

c, the wave speed, is given by the ratio of ω/k.Magnitude() . This is equivalent to the equation c = frequency * wavelength. This is how quickly each wave crest will appear to move through space.

φ is just a phase offset which in this case can probably just be set to zero but in general is used to set the phase at the origin.

The expression: (k•r - ωt + φ) is the total phase of the wave at the given position, r and time, t. It is a useful metric for comparing waves of different types.

A( r, t ) is the just a function of position and time and gives the amplitude of the wave at that position and time.

A_0 is the parameter used to set the maximum amplitude of the wave. The larger this is, the “taller” the waves will be.

For your wave we need to figure out what the vertical position of each of the grid points should be, this is the amplitude of the wave (give or take a constant offset to get the wave centred around the right height).

The basic equation for a wave is given by:

A( r, t ) = A_0 Sin( k•r - ωt + φ).

As the grid is in 2D and φ is somewhat irrelevant, it can be written like:

A( x, y, t) = A_0 Sin( k_x * x + k_y * y - ωt).

k_x and k_y just represent the x and y components of the wavevector. See this link for a plot of a snapshot of a wave travelling in the direction of (1,1). You can change the numbers in front of the x and y to change the direction and wavelength.

This is valid for any direction of travel, in your script you have the wave travelling in y direction (in the grid coordinates). This is the same as saying k_x = 0.

This leaves us with:

A( x, y, t ) = A( y, t ) = A_0 Sin( k_y * y - ωt).

In terms of your code this means setting grid.Points[x][y].Y = A( y, t ) in your loop. t will be the result os.clock() or tick(), something like that.

8 Likes