How would you store/model this map?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

The next step: take every second equidistant point and make here the cell; other second must be a pair of cells, but with normal shift as x + k*dy, y - k*dx (dx and dy is a tangent in the point).

Code: Select all

-- ladder-roads
-- license cc0, darkfrei 2023


TrackLine = 
-- bezier-roads, 11 pieces
{
	{0, 400, 10, 400}, -- line
	{10, 400, 123, 400, 203, 282}, -- bezier
	{203, 282, 279, 169, 452, 172}, -- bezier
	{452, 172, 518, 173, 569, 234}, -- bezier
	{569, 234, 625, 302}, -- line
	{625, 302, 664, 348, 639, 422}, -- bezier
	{639, 422, 610, 508, 472, 495}, -- bezier
	{472, 495, 407, 489, 359, 429}, -- bezier
	{359, 429, 306, 363, 189, 422}, -- bezier
	{189, 422, 136, 449, 73, 448}, -- bezier
	{73, 448, 4, 447}, -- line
}

-- restore bezier:
Line = {} -- the line as list of position pairs
for iRoad = 1, #TrackLine do
	local road = TrackLine[iRoad]
	if #road > 4 then -- bezier
		local bezierObj = love.math.newBezierCurve (road)
		local amount = 32
		for t = 0, amount-1 do -- don't add last point
			local x, y = bezierObj:evaluate (t/amount)
			table.insert (Line, x)
			table.insert (Line, y)
		end
	else
		for i = 1, #road-3, 2 do -- don't add last point
			table.insert (Line, road[i])
			table.insert (Line, road[i+1])
		end
	end
end


local function get_points_along_line (line, gap)
-- from https://github.com/darkfrei/love2d-lua-tests/blob/main/railway-track/railways.lua#L88
	local points = {}
	local tangents = {}
	local rest = gap/2 -- rest is gap to start point on this section
	local x1, y1, x2, y2, dx, dy = line[1],line[2]
	for i=3, #line-1, 2 do
		x2, y2 = line[i],line[i+1]
		dx, dy = x2-x1, y2-y1
		local sector_length = (dx*dx+dy*dy)^0.5
		if sector_length > rest then
			-- rest is always shorter than gap; sector is shorter than rest (or gap)
			dx, dy = dx/sector_length, dy/sector_length
			while sector_length > rest do
				local x, y = x1+rest*dx, y1+rest*dy
				table.insert (points, x)
				table.insert (points, y)
				table.insert (tangents, dx)
				table.insert (tangents, dy)
				rest = rest + gap
			end
		else -- no point in this distance
		end
		-- the tail for the next 
		rest = rest-sector_length
		x1, y1 = x2, y2
	end
	return points, tangents
end

--add  equidistant points
EquidistantPoints, Tangents = get_points_along_line (Line, 15)

-- cells as round
Cells = {}
for i = 1, #EquidistantPoints-1, 2 do
	-- position
	local x, y = EquidistantPoints[i], EquidistantPoints[i+1]
	-- tangent
	local dx, dy = Tangents[i], Tangents[i+1]
	if ((i-1)/2)%2 == 0 then
		-- side pair of cell
		local leftCell = {x=x+10*dy, y=y-10*dx, dx=dx, dy=dy, 
			left = #Cells+2, right = #Cells+3, next = #Cells+4}
		table.insert (Cells, leftCell)
		local rightCell = {x=x-10*dy, y=y+10*dx, dx=dx, dy=dy, 
			left = #Cells+2, right = #Cells+3, next = #Cells+4}
		table.insert (Cells, rightCell)
	else
		-- middle cell
		local cell = {x=x, y=y, dx=dx, dy=dy, 
			left = #Cells+2, right = #Cells+3, next = #Cells+4}
		table.insert (Cells, cell)
	end
end

function love.draw ()
	love.graphics.setColor (0.25,0.25, 0.25)
	love.graphics.line (Line)
--	for i = 1, #EquidistantPoints-1, 2 do
--		love.graphics.circle ('line', EquidistantPoints[i], EquidistantPoints[i+1], 3)
--	end
	
	love.graphics.setColor (1,1,1)
	for iCell, cell in ipairs (Cells) do
		love.graphics.circle ('line', cell.x, cell.y, 4)
	end
end
Screenshot 2023-04-10 191213.png
Screenshot 2023-04-10 191213.png (44.6 KiB) Viewed 1536 times
Attachments
ladder-roads-03.love
(1.28 KiB) Downloaded 58 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

And if we have position and angle, just draw here rotated rectangle:

Code: Select all

function drawRotatedRectangle(mode, x, y, w, h, angle)
	love.graphics.push()
		love.graphics.translate(x, y)
		love.graphics.rotate(angle)
		love.graphics.rectangle(mode, -w/2, -h/2, w, h)
	love.graphics.pop()
end

function love.draw ()
	love.graphics.setColor (0.25,0.25, 0.25)
	love.graphics.line (Line)
--	for i = 1, #EquidistantPoints-1, 2 do
--		love.graphics.circle ('line', EquidistantPoints[i], EquidistantPoints[i+1], 3)
--	end
	
	love.graphics.setColor (1,1,1)
	for iCell, cell in ipairs (Cells) do
--		love.graphics.circle ('line', cell.x, cell.y, 4)
		drawRotatedRectangle( 'line', cell.x, cell.y, 30, 10, cell.angle)
	end
end
Screenshot 2023-04-10 201837.png
Screenshot 2023-04-10 201837.png (59.01 KiB) Viewed 1524 times
Attachments
ladder-roads-04.love
(1.41 KiB) Downloaded 66 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

Next: Make the function where you can add/remove cells and rearrange the connections between them.
Use the union of cells to make long cells:
image-2023-04-06.png
image-2023-04-06.png (75.09 KiB) Viewed 1495 times
And than just make this points (actually we have x, y position and the angle in this point), where you have cell corners without intersections.

Polygons ABFB'A'E, CDHD'C'G, FGC'G'F'B' and the next points the same way.
You can also use two points and two tangents in this points to make smooth lines with bezier.
image-2023-04-11.png
image-2023-04-11.png (27.14 KiB) Viewed 1495 times

Code: Select all

function cubicBezier(px0, py0, angle0, px3, py3, angle3, curve) -- not tested
	-- tangent:
  local tx0, ty0 = math.cos(angle0), math.sin(angle0)
  local tx3, ty3 = math.cos(angle3), math.sin(angle3)
  
  local controlDist = math.sqrt((px3 - px0)^2 + (py3 - py0)^2)/3
  local px1, py1 = px0 + controlDist  * tx0, py0 + controlDist  * ty0
  local px2, py2 = px3 - controlDist  * tx3, py3 - controlDist  * ty3
  
  curve = curve or {}
  local amount = 8
  for n = 0, amount-1  do -- not the last point
  	local t = n/amount
  	local a, b, c, d = (1-t)^3, 3*t*(1-t)^2, 3*t^2*(1-t), t^3
	local x = a*px0+b*px1+c*px2+d*px3
	local y = a*py0+b*py1+c*py2+d*py3
 	table.insert(curve, x)
	table.insert(curve, y)
  end
  return curve
end
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3691
Joined: Sun Oct 18, 2015 2:58 pm

Re: How would you store/model this map?

Post by pgimeno »

Bézier curves can be split at any point. I think that feature could help here to make two adjacent tile borders match.

You can also use the derivative of a Bézier curve to get a binormal, and from that, a normal, so that tiles at curves are perpendicular to the border.
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

pgimeno wrote: Tue Apr 11, 2023 5:36 pm Bézier curves can be split at any point. I think that feature could help here to make two adjacent tile borders match.

You can also use the derivative of a Bézier curve to get a binormal, and from that, a normal, so that tiles at curves are perpendicular to the border.
You are right, but my code was for any line, for example perfect circle, made of any amount of segments.

There is the ready code (ALOT of code optimization possible):
https://github.com/darkfrei/love2d-lua- ... dder-roads

Code: Select all

-- ladder-roads
-- license cc0, darkfrei 2023


TrackLine = 
-- bezier-roads, 11 pieces
{
	{0, 400, 10, 400}, -- line
	{10, 400, 123, 400, 203, 282}, -- bezier
	{203, 282, 279, 169, 452, 172}, -- bezier
	{452, 172, 518, 173, 569, 234}, -- bezier
	{569, 234, 625, 302}, -- line
	{625, 302, 664, 348, 639, 422}, -- bezier
	{639, 422, 610, 508, 472, 495}, -- bezier
	{472, 495, 407, 489, 359, 429}, -- bezier
}

CellLength = 80
CellWidth = 30

function insertPair (list, x, y)
	table.insert (list, x)
	table.insert (list, y)
end

-- restore bezier:
Line = {} -- the line as list of position pairs
for iRoad = 1, #TrackLine do
	local road = TrackLine[iRoad]
	if #road > 4 then -- bezier
		local bezierObj = love.math.newBezierCurve (road)
		local amount = 32
		for t = 0, amount-1 do -- don't add last point
			local x, y = bezierObj:evaluate (t/amount)
			table.insert (Line, x)
			table.insert (Line, y)
		end
	else
		for i = 1, #road-3, 2 do -- don't add last point
			table.insert (Line, road[i])
			table.insert (Line, road[i+1])
		end
	end
end


local function get_points_along_line (line, gap)
-- from https://github.com/darkfrei/love2d-lua-tests/blob/main/railway-track/railways.lua#L88
	local points = {}
	local tangents = {}
	local rest = 0
--	local rest = gap/2 -- rest is gap to start point on this section
	local x1, y1, x2, y2, dx, dy = line[1],line[2]
	for i=3, #line-1, 2 do
		x2, y2 = line[i],line[i+1]
		dx, dy = x2-x1, y2-y1
		local sector_length = (dx*dx+dy*dy)^0.5
		if sector_length > rest then
			-- rest is always shorter than gap; sector is shorter than rest (or gap)
			dx, dy = dx/sector_length, dy/sector_length
			while sector_length > rest do
				local x, y = x1+rest*dx, y1+rest*dy
				table.insert (points, x)
				table.insert (points, y)
				table.insert (tangents, dx)
				table.insert (tangents, dy)
				rest = rest + gap
			end
		else -- no point in this distance
		end
		-- the tail for the next 
		rest = rest-sector_length
		x1, y1 = x2, y2
	end
	return points, tangents
end

local lineLength = 0
local x1, y1 = Line[1], Line[2]
for i = 3, #Line-1, 2 do
	local x2, y2 = Line[i], Line[i+1]
	lineLength = lineLength + math.sqrt((x2-x1)^2 + (y2-y1)^2)
	x1, y1 = x2, y2
end
CellLength = lineLength / math.floor(lineLength/CellLength +0.5)
print ('middle cells count:', math.floor(lineLength/CellLength +0.5))
print ('CellLength:', CellLength)

--add	equidistant points
EquidistantPoints, Tangents = get_points_along_line (Line, CellLength/2)

-- cells as round
Cells = {}


function cubicBezier(x1, y1, angle1, x2, y2, angle2, curve) -- not tested
--	print (x1, y1, angle1, x2, y2, angle2)
	curve = curve or {}
	local dx1, dy1 = math.cos (angle1), math.sin (angle1)
--	print (dx1, dy1)
	local dx2, dy2 = math.cos (angle2), math.sin (angle2)
--	local dx1, dy1 = math.sin (angle1), math.cos (angle1)
--	local dx2, dy2 = math.sin (angle2), math.cos (angle2)
	local dist = math.sqrt((x2-x1)^2+(y2-y1)^2)/3
	local px1, py1 = x1+dist*dx1, y1+dist*dy1
--	print (px1, py1)
	local px2, py2 = x2-dist*dx2, y2-dist*dy2
	local amount = 3
	for n = 0, amount-1	do -- not the last point
		local t = n/amount
		local a, b, c, d = (1-t)^3, 3*t*(1-t)^2, 3*t^2*(1-t), t^3
		local x = a*x1+b*px1+c*px2+d*x2
		local y = a*y1+b*py1+c*py2+d*y2
		table.insert(curve, x)
		table.insert(curve, y)
	end
	return curve
end

function createCell (typ, x, y, dx, dy, angle, CellWidth, amount)
	if typ == 'left' then
		-- left lane has transitions to cell above or to right (middle lane):
			local cell = {x=x+CellWidth*dy, y=y-CellWidth*dx, dx=dx, dy=dy, angle=angle,
			left = nil, right = amount+3, next = amount+4}
		return cell
	elseif typ == 'right' then
		local cell = {x=x-CellWidth*dy, y=y+CellWidth*dx, dx=dx, dy=dy, angle=angle,
			left = amount+2, right = nil, next = amount+4}
		return cell
	else -- middle
		local cell = {x=x, y=y, dx=dx, dy=dy, angle=angle,
			left = amount+2, right = amount+3, next = amount+4}
		return cell
	end
end

BorderA = {} -- line
BorderB = {}
BorderC = {}
BorderD = {}

Sprits = {} -- list of lines

local angleOld

-- iterate all equidistance points:
for indexEP = 1, #EquidistantPoints-1, 2 do
	-- position
	local x, y = EquidistantPoints[indexEP], EquidistantPoints[indexEP+1]
	-- tangent
	local dx, dy = Tangents[indexEP], Tangents[indexEP+1]
	local angle = math.atan2(dy, dx)
	angleOld = angleOld or angle
	if ((indexEP-1)/2)%2 == 1 then
		local leftCell = createCell ('left', x, y, dx, dy, angle, CellWidth, #Cells)
		table.insert (Cells, leftCell)
		local rightCell = createCell ('right', x, y, dx, dy, angle, CellWidth, #Cells)
		table.insert (Cells, rightCell)
	else
		local cell = createCell ('middle', x, y, dx, dy, angle, CellWidth, #Cells)
		table.insert (Cells, cell)
	end
	
	local x2, y2 = EquidistantPoints[indexEP+2], EquidistantPoints[indexEP+3]
	local dx2, dy2 = Tangents[indexEP+2], Tangents[indexEP+3]
	if not dy2 then
		dx2, dy2 = dx, dy
		local pointAx, pointAy = x+1.5*CellWidth*dy, y-1.5*CellWidth*dx
		local pointBx, pointBy = x+0.5*CellWidth*dy, y-0.5*CellWidth*dx
		local pointCx, pointCy = x-0.5*CellWidth*dy, y+0.5*CellWidth*dx
		local pointDx, pointDy = x-1.5*CellWidth*dy, y+1.5*CellWidth*dx
		
		table.insert (BorderA, pointAx)
		table.insert (BorderA, pointAy)
		
		table.insert (BorderB, pointBx)
		table.insert (BorderB, pointBy)
		
		table.insert (BorderC, pointCx)
		table.insert (BorderC, pointCy)
		
		table.insert (BorderD, pointDx)
		table.insert (BorderD, pointDy)
		
		if ((indexEP-1)/2)%2 == 1 then
			-- odd
			table.insert (Sprits, {pointBx,  pointBy, pointCx,  pointCy})
		else
			-- even
			table.insert (Sprits, {pointAx, pointAy, pointBx, pointBy})
			table.insert (Sprits, {pointCx, pointCy, pointDx, pointDy})
		end
	else
		local nextAngle = math.atan2(dy2, dx2)
		local pointAx,  pointAy =  x +1.5*CellWidth*dy,  y -1.5*CellWidth*dx
		local pointA2x, pointA2y = x2+1.5*CellWidth*dy2, y2-1.5*CellWidth*dx2
		
		local pointBx,  pointBy =  x +0.5*CellWidth*dy,  y -0.5*CellWidth*dx
		local pointB2x, pointB2y = x2+0.5*CellWidth*dy2, y2-0.5*CellWidth*dx2
		
		local pointCx,  pointCy =  x -0.5*CellWidth*dy,  y +0.5*CellWidth*dx
		local pointC2x, pointC2y = x2-0.5*CellWidth*dy2, y2+0.5*CellWidth*dx2
		
		local pointDx,  pointDy =  x -1.5*CellWidth*dy,  y +1.5*CellWidth*dx
		local pointD2x, pointD2y = x2-1.5*CellWidth*dy2, y2+1.5*CellWidth*dx2
		
		cubicBezier(pointAx, pointAy, angle, pointA2x, pointA2y, nextAngle, BorderA)
		cubicBezier(pointBx, pointBy, angle, pointB2x, pointB2y, nextAngle, BorderB)
		cubicBezier(pointCx, pointCy, angle, pointC2x, pointC2y, nextAngle, BorderC)
		cubicBezier(pointDx, pointDy, angle, pointD2x, pointD2y, nextAngle, BorderD)
		
		if ((indexEP-1)/2)%2 == 1 then
			-- odd
			table.insert (Sprits, {pointBx,  pointBy, pointCx,  pointCy})
		else
			-- even
			table.insert (Sprits, {pointAx, pointAy, pointBx, pointBy})
			table.insert (Sprits, {pointCx, pointCy, pointDx, pointDy})
		end
	end

	
	angleOld = angle
end




for iCell, cell in ipairs (Cells) do
	local nextCell = Cells[cell.next]
	if nextCell then
		local x1, y1 = cell.x, cell.y
		local x2, y2 = nextCell.x, nextCell.y
		local line = {x1, y1, x2, y2}
		cell.nextLine = line
	end
	local rightCell = Cells[cell.right]
	if rightCell then
		local x1, y1 = cell.x, cell.y
		local x2, y2 = rightCell.x, rightCell.y
		local line = {x1, y1, x2, y2}
		cell.rightLine = line
	end
	local leftCell = Cells[cell.left]
	if leftCell then
		local x1, y1 = cell.x, cell.y
		local x2, y2 = leftCell.x, leftCell.y
		local line = {x1, y1, x2, y2}
		cell.leftLine = line
	end	
end

	
function drawRotatedRectangle(mode, x, y, w, h, angle)
	love.graphics.push()
		love.graphics.translate(x, y)
		love.graphics.rotate(angle)
		love.graphics.rectangle(mode, -w/2, -h/2, w, h)
	love.graphics.pop()
end

function love.draw ()
	
	love.graphics.setLineWidth (1)
	love.graphics.setColor (0.25,0.25, 0.25)
	love.graphics.line (Line)
	for i = 1, #EquidistantPoints-1, 2 do
		love.graphics.circle ('line', EquidistantPoints[i], EquidistantPoints[i+1], 3)
	end
	
	--[[
	for iCell, cell in ipairs (Cells) do
		love.graphics.setLineWidth (2)
		love.graphics.setColor (0.5,0.5,0)
		love.graphics.circle ('line', cell.x, cell.y, 4)
		love.graphics.setColor (1,1,1)
		drawRotatedRectangle( 'line', cell.x, cell.y, CellLength, CellWidth, cell.angle)
		
		love.graphics.setLineWidth (1)
		if cell.nextLine then
			love.graphics.setColor (1,1,0,0.75)
			love.graphics.line (cell.nextLine)
		end
		if cell.rightLine then
			love.graphics.setColor (0,1,0,0.75)
			love.graphics.line (cell.rightLine)
		end
		if cell.leftLine then
			love.graphics.setColor (1,0,0,0.75)
			love.graphics.line (cell.leftLine)
		end
	end
	]]
	
	love.graphics.setLineWidth (4)
	love.graphics.setColor (1,1,1)
	love.graphics.line (BorderA)
	love.graphics.line (BorderB)
	love.graphics.line (BorderC)
	love.graphics.line (BorderD)
	
	for i, line in ipairs (Sprits) do
		love.graphics.line (line)
	end
end
Examples with 180 pixels, 80 pixels and 20 pixels middle cells lengths (they are adjusted to the whole track):
2023-04-16T17_36_46-Untitled.png
2023-04-16T17_36_46-Untitled.png (57.44 KiB) Viewed 1304 times
2023-04-16T17_31_37-Untitled.png
2023-04-16T17_31_37-Untitled.png (61.43 KiB) Viewed 1306 times
2023-04-16T17_31_47-Untitled.png
2023-04-16T17_31_47-Untitled.png (82.54 KiB) Viewed 1306 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
knorke
Party member
Posts: 275
Joined: Wed Jul 14, 2010 7:06 pm
Contact:

Re: How would you store/model this map?

Post by knorke »

I am impressed by how quickly you figured out a solution and posted functional examples.
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 7 guests