Snake, lizard, fish, robot (train) procedural animation

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
darkfrei
Party member
Posts: 1262
Joined: Sat Feb 08, 2020 11:09 pm

Snake, lizard, fish, robot (train) procedural animation

Post by darkfrei »

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
Last edited by darkfrei on Wed Apr 02, 2025 4:18 pm, edited 2 times in total.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
SETTORB
Prole
Posts: 1
Joined: Thu Feb 20, 2025 11:44 am

Re: Snake, lizard, fish, robot (train) procedural animation

Post by SETTORB »

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.
User avatar
darkfrei
Party member
Posts: 1262
Joined: Sat Feb 08, 2020 11:09 pm

Re: Snake, lizard, fish, robot (train) procedural animation

Post by darkfrei »

First is the simplest working simulation:
Animation (99).gif
Animation (99).gif (294.96 KiB) Viewed 4219 times

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
Or with Lissajous figure:
Animation (100).gif
Animation (100).gif (310.08 KiB) Viewed 4196 times

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 52 times
Last edited by darkfrei on Sat Feb 22, 2025 10:51 am, edited 1 time in total.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1262
Joined: Sat Feb 08, 2020 11:09 pm

Re: Snake, lizard, fish, robot (train) procedural animation

Post by darkfrei »

Added hard angle limit for the chain links:
Animation (101).gif
Animation (101).gif (303.36 KiB) Viewed 4201 times

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
With soft limits:
Animation (102).gif
Animation (102).gif (255.62 KiB) Viewed 4192 times

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 48 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1262
Joined: Sat Feb 08, 2020 11:09 pm

Re: Snake, lizard, fish, robot (train) procedural animation

Post by darkfrei »

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.

:awesome:
Animation (104).gif
Animation (104).gif (144.97 KiB) Viewed 4158 times

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 44 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
RNavega
Party member
Posts: 465
Joined: Sun Aug 16, 2020 1:28 pm

Re: Snake, lizard, fish, robot (train) procedural animation

Post by RNavega »

Cool thread. Reminds me of the "Pulled String" mode from Lazy Nezumi, a pen stabilizer utility for artists using graphics tablets. Notice how the blue gizmo bends when the mouse isn't stretching it.
lnpPulledString.gif
lnpPulledString.gif (15.72 KiB) Viewed 4049 times
(I appreciate that some art software like Krita, Paint Tool SAI and Clip Studio Paint have these pen stabilization features built-in.)
User avatar
darkfrei
Party member
Posts: 1262
Joined: Sat Feb 08, 2020 11:09 pm

Re: Snake, lizard, fish, robot (train) procedural animation

Post by darkfrei »

The inverse kinematics in the Love2D:

:awesome:
Animation (106).gif
Animation (106).gif (241.77 KiB) Viewed 3703 times

Code: Select all


-- chain parameters
local totalSum = 400
local numSegments = 8
local segmentLength = totalSum / numSegments

-- list of polyline points
local points = {}

-- fixed start point
local startPoint = {x = 400, y = 580}

-- target point (where we want to reach)
local targetPoint = {x = 600, y = 300}

-- end of the chain (may not reach target)
local endPoint = {x = 600, y = 300}

--Lissajous
local a, b = 150, 150        -- amplitudes for the trajectory
local omegaX, omegaY = 1, 2   -- frequencies for the trajectory
local phase = 0             -- phase shift
local time = 0

-- initialize points
for i = 0, numSegments do
	table.insert(points, {x = startPoint.x + i * segmentLength, y = startPoint.y})
end

-- fabrik inverse kinematics function

-- function to calculate distance between two points
local function distance(x1, y1, x2, y2)
	return math.sqrt((x2 - x1)^2 + (y2 - y1)^2)
end

-- function to calculate angle between two points
local function angleBetween(x1, y1, x2, y2)
	return math.atan2(y2 - y1, x2 - x1)
end


local function solveIK_FABRIK()
	-- calculate distance to target
	local distToTarget = distance(startPoint.x, startPoint.y, targetPoint.x, targetPoint.y)

	-- if target is too far, move endPoint to the maximum possible distance
	if distToTarget > totalSum then
		local angle = angleBetween(startPoint.x, startPoint.y, targetPoint.x, targetPoint.y)
		endPoint.x = startPoint.x + math.cos(angle) * totalSum
		endPoint.y = startPoint.y + math.sin(angle) * totalSum
	else
		endPoint.x = targetPoint.x
		endPoint.y = targetPoint.y
	end

	-- forward pass (pull end point toward target)
	points[#points].x = endPoint.x
	points[#points].y = endPoint.y

	for i = #points - 1, 1, -1 do
		local d = distance(points[i].x, points[i].y, points[i+1].x, points[i+1].y)
		local angle = angleBetween(points[i+1].x, points[i+1].y, points[i].x, points[i].y)
		local ratio = segmentLength / d

		points[i].x = points[i+1].x + (points[i].x - points[i+1].x) * ratio
		points[i].y = points[i+1].y + (points[i].y - points[i+1].y) * ratio
	end

	-- backward pass (fix start point)
	points[1].x = startPoint.x
	points[1].y = startPoint.y

	for i = 2, #points do
		local d = distance(points[i].x, points[i].y, points[i-1].x, points[i-1].y)
		local angle = angleBetween(points[i-1].x, points[i-1].y, points[i].x, points[i].y)
		local ratio = segmentLength / d

		points[i].x = points[i-1].x + math.cos(angle) * segmentLength
		points[i].y = points[i-1].y + math.sin(angle) * segmentLength
	end
end

function love.update(dt)
	time = time + dt
	targetPoint.x = 400 + a * math.sin(omegaX * time)
	targetPoint.y = 300 + b * math.sin(omegaY * time + phase)
	solveIK_FABRIK()
end

-- rendering function
function love.draw()
	-- solve ik
	solveIK_FABRIK()

	-- draw target point (mouse)
	love.graphics.setColor(0, 1, 0) -- green
	love.graphics.circle("fill", targetPoint.x, targetPoint.y, 5)

	-- draw lines and points
	for i = 1, #points - 1 do
		love.graphics.setColor(1, 1, 1) -- white
		love.graphics.line(points[i].x, points[i].y, points[i+1].x, points[i+1].y)
		love.graphics.circle("fill", points[i].x, points[i].y, 5)
	end

	-- draw end point
	love.graphics.circle("fill", endPoint.x, endPoint.y, 5)
end

-- mouse movement handler
--function love.mousemoved(x, y, dx, dy, istouch)
--	targetPoint.x = x
--	targetPoint.y = y
--	solveIK_FABRIK()
--end

:awesome:
Update!
The hard angle limiting links in the IK chains!

:awesome:
Animation (107).gif
Animation (107).gif (297.5 KiB) Viewed 3343 times

Code: Select all

-- chain parameters
local totalSum = 400
local numSegments = 16
local segmentLength = totalSum / numSegments
local maxAngle = math.rad(20) -- maximum allowed rotation in radians

-- list of polyline points
local points = {}

-- fixed start point
local startPoint = {x = 400, y = 580}

-- target point (where we want to reach)
local targetPoint = {x = 600, y = 300}

-- end of the chain (may not reach target)
local endPoint = {x = 600, y = 300}

-- initialize points
for i = 0, numSegments do
	table.insert(points, {x = startPoint.x + i * segmentLength, y = startPoint.y})
end

-- function to calculate distance between two points
local function distanceP (p1, p2)
	return math.sqrt((p2.x - p1.x)^2 + (p2.y - p1.y)^2)
end

-- function to calculate angle between two points
local function angleBetweenP (p1, p2)
	return math.atan2(p2.y - p1.y, p2.x - p1.x)
end

function math.sign(v)
	if v > 0 then
		return 1
	elseif v < 0 then
		return -1
	else
		return 0
	end
end



-- function to clamp joint angle within limit
local function clampJointAngle (baseAngle, angleDiff)
	angleDiff = ((angleDiff-baseAngle) + math.pi) % (2 * math.pi) - math.pi
	local absAngle = math.min (maxAngle, math.abs (angleDiff))
	local resultAngle = baseAngle + math.sign(angleDiff) * absAngle
	return resultAngle
end


-- function to solve inverse kinematics using the fabrik algorithm
local function solveIK_FABRIK()
	-- forward pass (pull end point toward target)
	points[#points].x = targetPoint.x
	points[#points].y = targetPoint.y

	for i = #points - 1, 1, -1 do
		local p1, p2, p3 = points[i], points[i+1], points[i+2]
		local d = distanceP(p1, p2)
		local ratio = segmentLength / d

		-- adjust the position of the next segment
		p1.x = p2.x + (p1.x - p2.x) * ratio
		p1.y = p2.y + (p1.y - p2.y) * ratio

		if p3 then
			local a1 = angleBetweenP(p1, p2)
			local a2 = angleBetweenP(p2, p3)
			local a3 = clampJointAngle (a2, a1)
			p1.x = p2.x - segmentLength*math.cos (a3)
			p1.y = p2.y - segmentLength*math.sin (a3)
		end
	end

	-- backward pass (fix start point)
	points[1].x = startPoint.x
	points[1].y = startPoint.y

	-- propagate adjustments forward from the start point
	for i = 1, #points-1 do
		local p1, p2, p3 = points[i], points[i+1], points[i+2]

		local d = distanceP(p1, p2)
		local ratio = segmentLength / d

		-- adjust the position of the next segment
		p2.x = p1.x + (p2.x - p1.x) * ratio
		p2.y = p1.y + (p2.y - p1.y) * ratio

		if p3 then
			local a1 = angleBetweenP(p1, p2)
			local a2 = angleBetweenP(p2, p3)
			local a3 = clampJointAngle (a1, a2)
			p3.x = p2.x + math.cos(a3) * segmentLength
			p3.y = p2.y + math.sin(a3) * segmentLength
		end
	end

	-- update end point position
	endPoint.x = points[#points].x
	endPoint.y = points[#points].y
end


-- parameters for a lissajous curve trajectory
local a, b = 150, 150        -- amplitudes for the trajectory
local omegaX, omegaY = 1, 2   -- frequencies for the trajectory
local phase = 0             -- phase shift
local time = 0

-- update function, moving the target along a lissajous curve
function love.update(dt)
	time = time + dt
	targetPoint.x = 400 + a * math.sin(omegaX * time)
	targetPoint.y = 300 + b * math.sin(omegaY * time + phase)
	solveIK_FABRIK()
end

-- rendering function
function love.draw()
	-- solve ik
	solveIK_FABRIK()

	-- draw target point (mouse)
	love.graphics.setColor(0, 1, 0) -- green
	love.graphics.circle("fill", targetPoint.x, targetPoint.y, 5)

	-- draw lines and points
	for i = 1, #points - 1 do
		love.graphics.setColor(1, 1, 1) -- white
		love.graphics.line(points[i].x, points[i].y, points[i+1].x, points[i+1].y)
		love.graphics.circle("fill", points[i].x, points[i].y, 5)
	end

	-- draw end point
	love.graphics.circle("fill", endPoint.x, endPoint.y, 5)
end

-- mouse movement handler
--function love.mousemoved(x, y, dx, dy, istouch)
--	targetPoint.x = x
--	targetPoint.y = y
--	solveIK_FABRIK()
--end
Attachments
inverse-kinematics-02.love
(1.43 KiB) Downloaded 41 times
2025-02-23T17_43_33-Untitled.png
2025-02-23T17_43_33-Untitled.png (3.84 KiB) Viewed 3703 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot] and 8 guests