Hi all! I've found a video with something interesting for me! Let's make similar simulatios together!
https://www.youtube.com/watch?v=qlfh_rv6khY
Snake, lizard, fish, robot (train) procedural animation
Re: Snake, lizard, fish, robot (train) procedural animation
I am also interested in making these kind of animations with love2d.
I tried ton convert the source code from java (I have no skill in this langage) : (https://github.com/argonautcode/animal-proc-anim) to lua/love2d, with chat Gpt and (https://app.codeconvert.ai/home) but it's kind of a mess (I am a beginner). The fish animation was working but the final rendering was quite far from the one on the video.
Instead of doing that, I am heading to start from skratch, using my brain.
I tried ton convert the source code from java (I have no skill in this langage) : (https://github.com/argonautcode/animal-proc-anim) to lua/love2d, with chat Gpt and (https://app.codeconvert.ai/home) but it's kind of a mess (I am a beginner). The fish animation was working but the final rendering was quite far from the one on the video.
Instead of doing that, I am heading to start from skratch, using my brain.
Re: Snake, lizard, fish, robot (train) procedural animation
First is the simplest working simulation:
Or with Lissajous figure:
Code: Select all
local chain = {}
local segmentLength = 20
local chainLength = 10
function love.load()
-- create the chain with evenly spaced nodes
for i = 1, chainLength do
local x = 100 + (chainLength - i) * segmentLength
local y = 100
chain[i] = { x = x, y = y }
end
end
function love.update(dt)
-- move the first node to follow the mouse
local mx, my = love.mouse.getPosition()
local prev = chain[1]
prev.x, prev.y = mx, my
-- update the rest of the chain
for i = 2, #chain do
local node = chain[i]
-- calculate the vector between nodes
local dx, dy = node.x - prev.x, node.y - prev.y
local dist = math.sqrt(dx * dx + dy * dy)
if dist > 0 then
local move = segmentLength-dist
if dist > 0 then
-- adjust the position to maintain segment length
node.x = node.x + (dx / dist) * move
node.y = node.y + (dy / dist) * move
end
end
prev = node
end
end
function love.draw()
-- draw nodes and connect them with lines
love.graphics.setColor (1,1,1)
for i = 1, #chain do
love.graphics.circle('fill', chain[i].x, chain[i].y, 5)
if i > 1 then
love.graphics.line(chain[i].x, chain[i].y, chain[i - 1].x, chain[i - 1].y)
end
end
end
Code: Select all
local chain = {}
local segmentLength = 20
local chainLength = 20
local time = 0
function love.load()
-- create the chain with evenly spaced nodes
for i = 1, chainLength do
local x = 100 + (chainLength - i) * segmentLength
local y = 100
chain[i] = { x = x, y = y }
end
end
function love.update(dt)
-- move the first node to follow the mouse
-- local mx, my = love.mouse.getPosition()
-- move the first node along a lissajous curve (figure-eight)
local a, b = 150, 150 -- amplitude
local omegaX, omegaY = 1, 2 -- frequency
-- local phase = math.pi / 2 -- phase shift
local phase =0 -- phase shift
time = time + dt -- control speed of movement
-- lissajous figure parameters
local delta = math.pi / 2
local prev = chain[1]
-- move the first node along a lissajous figure
prev.x = 400 + a * math.sin(omegaX * time)
prev.y = 300 + b * math.sin(omegaY * time + phase)
-- update the rest of the chain
for i = 2, #chain do
local node = chain[i]
-- calculate the vector between nodes
local dx, dy = node.x - prev.x, node.y - prev.y
local dist = math.sqrt(dx * dx + dy * dy)
if dist > 0 then
local move = segmentLength-dist
if dist > 0 then
-- adjust the position to maintain segment length
node.x = node.x + (dx / dist) * move
node.y = node.y + (dy / dist) * move
end
end
prev = node
end
end
function love.draw()
-- draw nodes and connect them with lines
love.graphics.setColor (1,1,1)
for i = 1, #chain do
love.graphics.circle('fill', chain[i].x, chain[i].y, 5)
if i > 1 then
love.graphics.line(chain[i].x, chain[i].y, chain[i - 1].x, chain[i - 1].y)
end
end
end
- Attachments
-
chain-movement-01.love
- (664 Bytes) Downloaded 3 times
Last edited by darkfrei on Sat Feb 22, 2025 10:51 am, edited 1 time in total.
Re: Snake, lizard, fish, robot (train) procedural animation
Added hard angle limit for the chain links:
With soft limits:
Code: Select all
local chain = {}
local segmentLength = 20
local chainLength = 20
local time = 0
local maxAngle = math.rad(20)
-- lissajous figure parameters
local a, b = 150, 150 -- amplitude
local omegaX, omegaY = 1, 2 -- frequency
local phase =0 -- phase shift
local delta = math.pi / 2
function love.load()
-- create the chain with evenly spaced nodes
for i = 1, chainLength do
local x = 100 + (chainLength - i) * segmentLength
local y = 100
chain[i] = { x = x, y = y }
end
end
function math.sign(v)
if v > 0 then
return 1
elseif v < 0 then
return -1
else
return 0
end
end
-- function to return node position if the angle between segments exceeds maxAngle
local function adjustNodePosition(x1, y1, x2, y2, x3, y3)
-- calculate the vectors between the points
local dx1, dy1 = x2 - x1, y2 - y1
local dx2, dy2 = x3 - x2, y3 - y2
-- calculate the angles of the vectors
local angle1 = math.atan2(dy1, dx1) -- angle of the vector from (x1, y1) to (x2, y2)
local angle2 = math.atan2(dy2, dx2) -- angle of the vector from (x2, y2) to (x3, y3)
-- calculate the difference between the two angles
local angleDiff = angle2 - angle1
-- normalize the angle difference to the range [-pi, pi]
if angleDiff > math.pi then
angleDiff = angleDiff - 2 * math.pi
elseif angleDiff < -math.pi then
angleDiff = angleDiff + 2 * math.pi
end
-- if the angle difference exceeds the max allowed angle, adjust the node position
if math.abs(angleDiff) > maxAngle then
-- limit the angle difference to the max angle
angleDiff = math.sign(angleDiff) * maxAngle
-- calculate the new angle for the second vector
local newAngle = angle1 + angleDiff
-- calculate the new position for the third node using the new angle
local length = math.sqrt(dx2 * dx2 + dy2 * dy2)
local newDx = math.cos(newAngle) * length
local newDy = math.sin(newAngle) * length
-- set the new position for the third node
x3 = x2 + newDx
y3 = y2 + newDy
end
return x3, y3
end
function love.update(dt)
time = time + dt -- control speed of movement
local prev = chain[1]
-- move the first node along a lissajous figure
prev.x = 400 + a * math.sin(omegaX * time)
prev.y = 300 + b * math.sin(omegaY * time + phase)
-- update the rest of the chain
for i = 2, #chain do
local node = chain[i]
-- calculate the vector between nodes
local dx, dy = node.x - prev.x, node.y - prev.y
local dist = math.sqrt(dx * dx + dy * dy)
if dist > 0 then
local move = segmentLength-dist
if dist > 0 then
-- adjust the position to maintain segment length
node.x = node.x + (dx / dist) * move
node.y = node.y + (dy / dist) * move
end
end
-- check the angle between segments and adjust if necessary
if i > 1 then
-- use the adjustNodePosition function to modify node[3] if needed
local nextNode = chain[i+1]
if nextNode then
local x3, y3 = adjustNodePosition(
prev.x, prev.y,
node.x, node.y,
nextNode.x, nextNode.y)
nextNode.x = x3
nextNode.y = y3
end
end
prev = node
end
end
function love.draw()
-- draw nodes and connect them with lines
love.graphics.setColor (1,1,1)
for i = 1, #chain do
love.graphics.circle('fill', chain[i].x, chain[i].y, 5)
if i > 1 then
love.graphics.line(chain[i].x, chain[i].y, chain[i - 1].x, chain[i - 1].y)
end
end
end
Code: Select all
local chain = {}
local segmentLength = 20
local chainLength = 20
local time = 0
local maxAngle = math.rad(20)
local maxOmega = math.rad(180*14)
print (maxOmega)
-- lissajous figure parameters
local a, b = 150, 150 -- amplitude
local omegaX, omegaY = 1, 2 -- frequency
local phase =0 -- phase shift
local delta = math.pi / 2
function love.load()
-- create the chain with evenly spaced nodes
for i = 1, chainLength do
local x = 100 + (chainLength - i) * segmentLength
local y = 100
chain[i] = { x = x, y = y }
end
end
function math.sign(v)
if v > 0 then
return 1
elseif v < 0 then
return -1
else
return 0
end
end
-- function to return node position with soft influence if the angle between segments exceeds maxAngle
local function adjustNodePosition(x1, y1, x2, y2, x3, y3, dt)
-- calculate the vectors between the points
local dx1, dy1 = x2 - x1, y2 - y1
local dx2, dy2 = x3 - x2, y3 - y2
-- calculate the angles of the vectors
local angle1 = math.atan2(dy1, dx1) -- angle of the vector from (x1, y1) to (x2, y2)
local angle2 = math.atan2(dy2, dx2) -- angle of the vector from (x2, y2) to (x3, y3)
-- calculate the difference between the two angles
local angleDiff = angle2 - angle1
-- normalize the angle difference to the range [-pi, pi]
if angleDiff > math.pi then
angleDiff = angleDiff - 2 * math.pi
elseif angleDiff < -math.pi then
angleDiff = angleDiff + 2 * math.pi
end
-- if the angle difference exceeds the max allowed angle, adjust the node position
if math.abs(angleDiff) > maxAngle then
-- limit the angle change to maxOmega * dt (soft adjustment)
local maxDelta = maxOmega * dt
local newMaxAngle = math.max (maxAngle, maxAngle + (math.abs(angleDiff)-maxAngle)*maxDelta)
-- print (maxDelta, maxAngle, math.abs(maxAngle)-maxDelta)
angleDiff = math.sign(angleDiff) * newMaxAngle
-- angleDiff = math.sign(angleDiff) * math.min(math.abs(angleDiff), maxDelta)
-- calculate the new angle for the second vector
local newAngle = angle1 + angleDiff
-- calculate the new position for the third node using the new angle
local length = math.sqrt(dx2 * dx2 + dy2 * dy2)
local newDx = math.cos(newAngle) * length
local newDy = math.sin(newAngle) * length
-- set the new position for the third node
x3 = x2 + newDx
y3 = y2 + newDy
end
return x3, y3
end
function love.update(dt)
time = time + dt -- control speed of movement
local prev = chain[1]
-- move the first node along a lissajous figure
prev.x = 400 + a * math.sin(omegaX * time)
prev.y = 300 + b * math.sin(omegaY * time + phase)
-- prev.x, prev.y = love.mouse.getPosition()
-- update the rest of the chain
for i = 2, #chain do
local node = chain[i]
-- calculate the vector between nodes
local dx, dy = node.x - prev.x, node.y - prev.y
local dist = math.sqrt(dx * dx + dy * dy)
if dist > 0 then
local move = segmentLength-dist
if dist > 0 then
-- adjust the position to maintain segment length
node.x = node.x + (dx / dist) * move
node.y = node.y + (dy / dist) * move
end
end
-- check the angle between segments and adjust if necessary
if i > 1 then
-- use the adjustNodePosition function to modify node[3] if needed
local nextNode = chain[i+1]
if nextNode then
local x3, y3 = adjustNodePosition(
prev.x, prev.y,
node.x, node.y,
nextNode.x, nextNode.y, dt)
nextNode.x = x3
nextNode.y = y3
end
end
prev = node
end
end
function love.draw()
-- draw nodes and connect them with lines
love.graphics.setColor (1,1,1)
for i = 1, #chain do
love.graphics.circle('fill', chain[i].x, chain[i].y, 5)
if i > 1 then
love.graphics.line(chain[i].x, chain[i].y, chain[i - 1].x, chain[i - 1].y)
end
end
end
- Attachments
-
chain-movement-02.love
- (1.45 KiB) Downloaded 3 times
Re: Snake, lizard, fish, robot (train) procedural animation
The train has other movement: the hard track without corner shortening. Also no limits for angles for the links, just do what the rail do.

Code: Select all
local chain = {} -- array of wagons (locomotive + cars)
local trail = {} -- array of locomotive position records (trail)
local trailMaxLength = 370 -- max number of positions in the trail
local segmentLength = 20 -- distance between wagons
local chainLength = 10 -- number of nodes (locomotive + wagons)
local a, b = 150, 150 -- amplitudes for the trajectory (Lissajous)
local omegaX, omegaY = 1, 2 -- frequencies for the trajectory
local phase = 0 -- phase shift
local time = 0
function love.load()
-- initialize the chain nodes. the first node is the locomotive, the rest are wagons
for i = 1, chainLength do
chain[i] = { x = 400, y = 300 }
end
end
-- function returns the position along the trail corresponding to the given distance from the start
local function getPositionAtDistance(distance)
local accumulated = 0
for j = 1, #trail - 1 do
local p1 = trail[j]
local p2 = trail[j + 1]
local dx = p1.x - p2.x
local dy = p1.y - p2.y
local d = math.sqrt(dx * dx + dy * dy)
if accumulated + d >= distance then
local ratio = (distance - accumulated) / d
local x = p1.x + (p2.x - p1.x) * ratio
local y = p1.y + (p2.y - p1.y) * ratio
return { x = x, y = y }, j
end
accumulated = accumulated + d
end
end
-- function to delete trail elements after a specified index
local function deleteTrailAfterIndex(endIndex)
for i = #trail, endIndex + 3, -1 do -- small tail overlap
table.remove(trail, i)
end
end
function love.update(dt)
time = time + dt
-- update the position of the locomotive (first node) along the Lissajous trajectory
local head = chain[1]
head.x = 400 + a * math.sin(omegaX * time)
head.y = 300 + b * math.sin(omegaY * time + phase)
-- the first wagon creates the path by recording its position
table.insert(trail, 1, { x = head.x, y = head.y })
if #trail > trailMaxLength then
table.remove(trail)
end
-- for each wagon, calculate its position along the trail
local pos, lastTrailIndex
for i = 2, chainLength do
local desiredDistance = (i - 1) * segmentLength
pos, lastTrailIndex = getPositionAtDistance(desiredDistance)
if pos then
chain[i].x = pos.x
chain[i].y = pos.y
end
end
-- delete the trail elements after the last wagon
if lastTrailIndex then
-- print ('delete lastTrailIndex', lastTrailIndex)
-- deleteTrailAfterIndex (lastTrailIndex)
end
end
function love.draw()
-- optionally: draw the locomotive trail for visibility
love.graphics.setColor(0, 1, 0)
for i = 1, #trail - 1 do
-- love.graphics.circle('line', trail[i].x, trail[i].y, 2)
love.graphics.line(trail[i].x, trail[i].y, trail[i + 1].x, trail[i + 1].y)
end
-- draw nodes and connect them with lines
love.graphics.setColor (1,1,1)
for i = 1, #chain do
love.graphics.circle('fill', chain[i].x, chain[i].y, 5)
-- if i > 1 then
-- love.graphics.line(chain[i].x, chain[i].y, chain[i - 1].x, chain[i - 1].y)
-- end
end
end
- Attachments
-
train-movement-01.love
- (1.23 KiB) Downloaded 3 times
Who is online
Users browsing this forum: Ahrefs [Bot] and 11 guests