Je l'ai essayé.
Éclaboussures
Comme le mentionne ce tutoriel , la surface de l'eau ressemble à un fil: si vous tirez sur un point du fil, les points situés à côté de ce point seront également abaissés. Tous les points sont également attirés vers une ligne de base.
En gros, il y a beaucoup de ressorts verticaux juxtaposés qui se tirent également.
J'ai dessiné ça à Lua avec LÖVE et j'ai obtenu ceci:
Cela semble plausible. Oh Hooke , mon beau génie.
Si vous voulez jouer avec, voici un portage JavaScript offert par Phil ! Mon code est à la fin de cette réponse.
Vagues de fond (sinus empilés)
Pour moi, les ondes de fond naturelles ressemblent à un groupe d’ondes sinusoïdales (d’amplitudes, de phases et de longueurs d’onde différentes) qui sont toutes combinées. Voici à quoi cela ressemblait quand je l'ai écrit:
Les modèles d'interférence semblent assez plausibles.
Tous ensemble maintenant
Il est donc assez simple de faire la somme des vagues splash et des vagues de fond:
Lorsque des éclaboussures se produisent, vous pouvez voir de petits cercles gris indiquant l'emplacement de la vague de fond d'origine.
Cela ressemble beaucoup à la vidéo que vous avez liée , alors je considérerais cela comme une expérience réussie.
Voici mon main.lua
(le seul fichier). Je pense que c'est assez lisible.
-- 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
Pour la solution (mathématiquement parlant, vous pouvez résoudre le problème de résolution d’équations différentielles, mais je suis sûr qu’ils ne le font pas de cette façon) en créant des vagues, vous avez 3 possibilités (selon le degré de détail à obtenir):
Solution 1
Vraiment simple, pour chaque onde, on calcule la distance (absolue) de chaque point de la surface à la source et on calcule le "haut" avec la formule
1.0f/(dist*dist) * sin(dist*FactorA + Phase)
où
Notez que nous pouvons ajouter autant de termes que nous le souhaitons (principe de superposition).
Pro
Contra
Solution 2
Pro
Contra
Solution 3
Maintenant, je frappe un mur dur, c'est la solution la plus compliquée.
Je n'ai pas implémenté celui-ci mais il est possible de résoudre ces monstres.
Ici vous pouvez trouver une présentation sur les mathématiques, ce n’est pas simple et il existe aussi des équations différentielles pour différents types d’ondes.
Voici une liste non complète avec quelques équations différentielles pour résoudre des cas plus particuliers (solitons, pics, ...)
Pro
Contra
Solution 4
Un peu plus compliqué que la solution 1 mais pas si compliqué une solution 3.
Nous utilisons des textures précalculées et les mélangeons ensemble, après quoi nous utilisons la cartographie des déplacements (en fait, une méthode pour les ondes 2D mais le principe peut également fonctionner pour des ondes 1D).
Le jeu sturmovik a utilisé cette approche, mais je ne trouve pas le lien vers l'article à ce sujet.
Pro
Contra
la source
Pour ajouter des ondes constantes, ajoutez quelques sinusoïdes après avoir calculé la dynamique. Pour des raisons de simplicité, je ferais de ce déplacement uniquement un effet graphique et je ne le laisserais pas affecter la dynamique elle-même, mais vous pouvez essayer les deux alternatives et voir celle qui fonctionne le mieux.
Pour réduire le "splashhole", je suggérerais de modifier la méthode Splash (index int, vitesse de flottement) afin qu'elle affecte directement non seulement l'index, mais également certains sommets proches, de manière à étaler l'effet tout en conservant le même effet " énergie". Le nombre de sommets affectés peut dépendre de la largeur de votre objet. Vous aurez probablement besoin de peaufiner l'effet avant d'obtenir un résultat parfait.
Pour texturer les parties les plus profondes de l'eau, vous pouvez soit procéder comme décrit dans l'article et rendre la partie plus profonde "plus bleue" ou interpoler entre deux textures en fonction de la profondeur de l'eau.
la source