Próbowałem tego.
Rozpryski (sprężyny)
Jak wspomniano w tym samouczku , powierzchnia wody jest jak drut: jeśli pociągniesz za jakiś punkt drutu, punkty znajdujące się obok tego punktu również zostaną pociągnięte w dół. Wszystkie punkty są również przyciągane z powrotem do linii bazowej.
Zasadniczo mnóstwo pionowych sprężyn obok siebie, które również się pociągają.
Naszkicowałem to w Lua za pomocą LÖVE i otrzymałem:
Wygląda na wiarygodne. Oh Hooke , ty przystojny geniuszu.
Jeśli chcesz się nim bawić, oto port JavaScript dzięki uprzejmości Phila ! Mój kod znajduje się na końcu tej odpowiedzi.
Fale w tle (ułożone sinusoidy)
Naturalne fale tła wyglądają dla mnie jak wiązka fal sinusoidalnych (o różnych amplitudach, fazach i długościach fal) zsumowanych. Oto jak to wyglądało, kiedy to napisałem:
Wzorce interferencji wyglądają całkiem prawdopodobne.
Teraz wszyscy razem
Zatem podsumowanie fal powitalnych i fal tła jest dość proste:
Kiedy dochodzi do rozprysków, widać małe szare kółka pokazujące, gdzie byłaby pierwotna fala tła.
Wygląda bardzo podobnie do tego filmu, który połączyłeś , więc uważam to za udany eksperyment.
Oto mój main.lua
(jedyny plik). Myślę, że to dość czytelne.
-- 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
Dla rozwiązania (matematycznie możesz rozwiązać problem z rozwiązywaniem równań różniczkowych, ale jestem pewien, że nie robią tego w ten sposób) tworzenia fal masz 3 możliwości (w zależności od tego, jak szczegółowy powinien być):
Rozwiązanie 1
Naprawdę proste, dla każdej fali obliczamy (absolutną) odległość od każdego punktu powierzchni do źródła i obliczamy „wysokość” za pomocą wzoru
1.0f/(dist*dist) * sin(dist*FactorA + Phase)
gdzie
Pamiętaj, że możemy dodać tyle terminów, ile chcemy (zasada superpozycji).
Zawodowiec
Contra
Rozwiązanie 2
Zawodowiec
Contra
Rozwiązanie 3
Teraz uderzyłem w twardą ścianę, jest to najbardziej skomplikowane rozwiązanie.
Nie wdrożyłem tego, ale możliwe jest rozwiązanie tych potworów.
Tutaj znajdziesz prezentację dotyczącą jego matematyki, nie jest to proste i istnieją również równania różniczkowe dla różnych rodzajów fal.
Oto niekompletna lista z pewnymi równaniami różniczkowymi do rozwiązania bardziej szczególnych przypadków (Solitons, Peakons, ...)
Zawodowiec
Contra
Rozwiązanie 4
Trochę bardziej skomplikowane niż rozwiązanie 1, ale nie tak skomplikowane rozwiązanie 3.
Używamy wstępnie obliczonych tekstur i łączymy je ze sobą, a następnie używamy mapowania przemieszczenia (właściwie metoda dla fal 2d, ale zasada może również działać dla fal 1d)
W tej grze wykorzystano takie podejście, ale nie znalazłem linku do artykułu na ten temat.
Zawodowiec
Contra
źródło
Aby dodać fale stałe, dodaj kilka fal sinusoidalnych po obliczeniu dynamiki. Dla uproszczenia chciałbym, aby to przesunięcie było jedynie efektem graficznym i nie pozwalało, aby miało to wpływ na samą dynamikę, ale można wypróbować obie alternatywy i przekonać się, które działa najlepiej.
Aby zmniejszyć rozmiar „splashhole”, sugerowałbym zmianę metody Splash (indeks wewnętrzny, prędkość zmiennoprzecinkowa), aby bezpośrednio wpływał nie tylko na indeks, ale także na niektóre bliskie wierzchołki, aby rozłożyć efekt, ale nadal mieć ten sam „ energia". Liczba dotkniętych wierzchołków może zależeć od tego, jak szeroki jest twój obiekt. Prawdopodobnie będziesz musiał dużo ulepszyć efekt, aby uzyskać doskonały wynik.
Aby teksturować głębsze części wody, możesz zrobić to tak, jak opisano w artykule i po prostu uczynić głębszą część „bardziej niebieskim” lub możesz interpolować między dwiema teksturami w zależności od głębokości wody.
źródło