Skip to main content
1 of 6
Anko
  • 13.4k
  • 10
  • 55
  • 82

Maybe the surface of the water is like a wire: If you pull on some point of the wire, the points next to that point will be pulled down too. All points are also attracted back to a baseline.

I sketched that idea in Lua using LÖVE and got this:

rough blue wave, formed of points, with red baseline

It's a plausible result.

I'm sure I've butchered the physics though: I have no idea what I'm doing beyond initial intuitions and lots of Hooke's Law!

Anyway, here's my main.lua. Feel free to read, copy or use as toilet paper.

-- Resolution of simulation
NUM_POINTS = 200
-- Width of simulation
WIDTH = 700
-- Spring constant for forces applied by adjacent points
SPRING_CONSTANT = 0.1
-- Sprint constant for force applied to baseline
SPRING_CONSTANT_BASELINE = 0.1
-- Vertical draw offset of simulation
Y_OFFSET = 300

-- Make points to go on the wave
function makeWavePoints(numPoints)
    local t = {}
    for n = 1,numPoints do
        -- This represents a point on the wave
        local newPoint = {
            x    = n / numPoints * WIDTH,
            y    = Y_OFFSET,
            spd = {y=0}, -- speed with vertical component zero
            mass = 1
        }
        t[n] = newPoint
    end
    return t
end

wavePoints = makeWavePoints(NUM_POINTS)

-- Update the positions of each wave point
function updateWavePoints(points, dt)
    for n,p in ipairs(points) do
        -- force to apply to this point
        local force = 0

        -- forces caused by the point immediately to the left or the right
        local forceFromLeft, forceFromRight

        if n == 1 then -- wrap to left-to-right
            local dy = points[# points].y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        else -- normally
            local dy = points[n-1].y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        end
        if n == # points then -- wrap to right-to-left
            local dy = points[1].y - p.y
            forceFromRight = SPRING_CONSTANT * dy
        else -- normally
            local dy = points[n+1].y - p.y
            forceFromRight = SPRING_CONSTANT * dy
        end

        -- Also apply force toward the baseline
        local dy = Y_OFFSET - p.y
        forceToBaseline = SPRING_CONSTANT_BASELINE * dy

        -- Sum up forces
        force = force + forceFromLeft
        force = force + forceFromRight
        force = force + forceToBaseline

        -- Calculate acceleration
        local acceleration = force / p.mass

        -- Apply acceleration (with damping)
        p.spd.y = 0.89 * p.spd.y + acceleration

        -- Apply speed
        p.y = p.y + p.spd.y
    end
end

-- Callback when updating
function love.update(dt)

    -- On click: Pick nearest point to mouse position
    if love.mouse.isDown("l") then
        local mouseX, mouseY = love.mouse.getPosition()
        local closestPoint = nil
        local closestDistance = nil
        for _,p in ipairs(wavePoints) do
            local distance = math.abs(mouseX-p.x)
            if closestDistance == nil then
                closestPoint = p
                closestDistance = distance
            else
                if distance <= closestDistance then
                    closestPoint = p
                    closestDistance = distance
                end
            end
        end

        closestPoint.y = love.mouse.getY()
    end

    -- Update positions of points
    updateWavePoints(wavePoints, dt)
end

local circle = love.graphics.circle
local line   = love.graphics.line
local color  = love.graphics.setColor
love.graphics.setBackgroundColor(0xff,0xff,0xff)

-- Callback for drawing
function love.draw(dt)

    -- Draw baseline
    color(0xff,0x33,0x33)
    line(0, Y_OFFSET, WIDTH, Y_OFFSET)

    -- Draw points and line
    color(0x00,0x33,0xbb)
    for n,p in ipairs(wavePoints) do
        circle("line", p.x, p.y, 3)
        if n == 1 then
        else
            local leftPoint = wavePoints[n-1]
            line(leftPoint.x, leftPoint.y, p.x, p.y)
        end
    end
end
Anko
  • 13.4k
  • 10
  • 55
  • 82