Lo intenté.
Salpicaduras (resortes)
Como se menciona en el tutorial , la superficie del agua es como un cable: si tira de algún punto del cable, los puntos al lado de ese punto también se tirarán hacia abajo. Todos los puntos también son atraídos de vuelta a una línea de base.
Básicamente, son muchos resortes verticales uno al lado del otro los que se tiran unos a otros también.
Dibujé eso en Lua usando LÖVE y obtuve esto:
Parece plausible Oh Hooke , genio guapo.
Si quieres jugar con él, ¡aquí hay un puerto JavaScript cortesía de Phil ! Mi código está al final de esta respuesta.
Ondas de fondo (senos apilados)
Las ondas de fondo natural me parecen un conjunto de ondas sinusoidales (con diferentes amplitudes, fases y longitudes de onda), todas sumadas. Esto es lo que parecía cuando lo escribí:
Los patrones de interferencia parecen bastante plausibles.
Todos juntos ahora
Entonces, es bastante simple sumar las ondas de bienvenida y las ondas de fondo:
Cuando se producen salpicaduras, puede ver pequeños círculos grises que muestran dónde estaría la onda de fondo original.
Se parece mucho al video que vinculó , por lo que lo consideraría un experimento exitoso.
Aquí está mi main.lua
(el único archivo). Creo que es bastante legible.
-- Resolution of simulation
NUM_POINTS = 50
-- Width of simulation
WIDTH = 400
-- Spring constant for forces applied by adjacent points
SPRING_CONSTANT = 0.005
-- Sprint constant for force applied to baseline
SPRING_CONSTANT_BASELINE = 0.005
-- Vertical draw offset of simulation
Y_OFFSET = 300
-- Damping to apply to speed changes
DAMPING = 0.98
-- Number of iterations of point-influences-point to do on wave per step
-- (this makes the waves animate faster)
ITERATIONS = 5
-- 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
-- A phase difference to apply to each sine
offset = 0
NUM_BACKGROUND_WAVES = 7
BACKGROUND_WAVE_MAX_HEIGHT = 5
BACKGROUND_WAVE_COMPRESSION = 1/5
-- Amounts by which a particular sine is offset
sineOffsets = {}
-- Amounts by which a particular sine is amplified
sineAmplitudes = {}
-- Amounts by which a particular sine is stretched
sineStretches = {}
-- Amounts by which a particular sine's offset is multiplied
offsetStretches = {}
-- Set each sine's values to a reasonable random value
for i=1,NUM_BACKGROUND_WAVES do
table.insert(sineOffsets, -1 + 2*math.random())
table.insert(sineAmplitudes, math.random()*BACKGROUND_WAVE_MAX_HEIGHT)
table.insert(sineStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
table.insert(offsetStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
end
-- This function sums together the sines generated above,
-- given an input value x
function overlapSines(x)
local result = 0
for i=1,NUM_BACKGROUND_WAVES do
result = result
+ sineOffsets[i]
+ sineAmplitudes[i] * math.sin(
x * sineStretches[i] + offset * offsetStretches[i])
end
return result
end
wavePoints = makeWavePoints(NUM_POINTS)
-- Update the positions of each wave point
function updateWavePoints(points, dt)
for i=1,ITERATIONS do
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 = DAMPING * p.spd.y + acceleration
-- Apply speed
p.y = p.y + p.spd.y
end
end
end
-- Callback when updating
function love.update(dt)
if love.keyboard.isDown"k" then
offset = offset + 1
end
-- 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 "drop line" from cursor
local mouseX, mouseY = love.mouse.getPosition()
line(mouseX, 0, mouseX, Y_OFFSET)
-- Draw click indicator
if love.mouse.isDown"l" then
love.graphics.circle("line", mouseX, mouseY, 20)
end
-- Draw overlap wave animation indicator
if love.keyboard.isDown "k" then
love.graphics.print("Overlap waves PLAY", 10, Y_OFFSET+50)
else
love.graphics.print("Overlap waves PAUSED", 10, Y_OFFSET+50)
end
-- Draw points and line
for n,p in ipairs(wavePoints) do
-- Draw little grey circles for overlap waves
color(0xaa,0xaa,0xbb)
circle("line", p.x, Y_OFFSET + overlapSines(p.x), 2)
-- Draw blue circles for final wave
color(0x00,0x33,0xbb)
circle("line", p.x, p.y + overlapSines(p.x), 4)
-- Draw lines between circles
if n == 1 then
else
local leftPoint = wavePoints[n-1]
line(leftPoint.x, leftPoint.y + overlapSines(leftPoint.x), p.x, p.y + overlapSines(p.x))
end
end
end
Para la solución (matemáticamente hablando, puede resolver el problema con la resolución de ecuaciones diferenciales, pero estoy seguro de que no lo hacen de esa manera) de crear ondas, tiene 3 posibilidades (dependiendo de qué tan detallado debe ser):
Solución 1
Realmente simple, para cada onda calculamos la distancia (absoluta) desde cada punto de la superficie hasta la fuente y calculamos la 'altura' con la fórmula
1.0f/(dist*dist) * sin(dist*FactorA + Phase)
dónde
Tenga en cuenta que podemos agregar tantos términos como queramos (principio de superposición).
Pro
Contra
Solución 2
Pro
Contra
Solución 3
Ahora golpeé una pared dura, esta es la solución más complicada.
No implementé este, pero es posible resolver estos monstruos.
Aquí puede encontrar una presentación sobre la matemática de la misma, no es simple y también existen ecuaciones diferenciales para diferentes tipos de ondas.
Aquí hay una lista no completa con algunas ecuaciones diferenciales para resolver casos más especiales (Solitones, Picos, ...)
Pro
Contra
Solución 4
Un poco más complicado que la solución 1, pero no una solución tan complicada 3.
Usamos texturas precalculadas y las combinamos, luego usamos el mapeo de desplazamiento (en realidad es un método para ondas 2D pero el principio también puede funcionar para ondas 1D)
El juego sturmovik ha utilizado este enfoque, pero no encuentro el enlace al artículo al respecto.
Pro
Contra
fuente
Para agregar ondas constantes, agregue un par de ondas sinusoidales después de haber calculado la dinámica. Por simplicidad, haría de este desplazamiento un efecto gráfico solamente y no dejaría que afectara la dinámica en sí, pero podría probar ambas alternativas y ver cuál funciona mejor.
Para hacer el "splashhole" más pequeño, sugeriría alterar el método Splash (int index, float speed) para que afecte directamente no solo el índice sino también algunos de los vértices cercanos, para extender el efecto pero seguir teniendo el mismo " energía". El número de vértices afectados podría depender de qué tan ancho sea su objeto. Probablemente necesites modificar mucho el efecto antes de obtener un resultado perfecto.
Para texturizar las partes más profundas del agua, puede hacer lo que se describe en el artículo y simplemente hacer que la parte más profunda sea "más azul" o puede interpolar entre dos texturas dependiendo de la profundidad del agua.
fuente