Code Doodles!

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Trystan
Prole
Posts: 15
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

Another quick doodle. This time soft-body physics with Verlet Integration. The gif didn't come out great but it's quite fun to play with.

You can grab and drag any particle with the mouse. F1 resets the cloth, F2 toggles a fan that blows from the side and F3 toggles colour.

I'm pretty sure I could have done this faster and easier with love.physics but I wanted to try my hand at implementing it from scratch.
Verlet.gif
Verlet.gif (5.24 MiB) Viewed 6688 times

Code: Select all

-- Disable output buffer so debug messages print in real time
io.stdout:setvbuf("no")

function love.load()
    love.window.setTitle("Verlet Physics")
    particles = {}
    sticks = {}
    screenWidth = love.graphics.getWidth()
    screenHeight = love.graphics.getHeight()
    gravity = 900
    drag = 0.03
    heldParticle = -1
    fan = 0
    colourMode = true
    createGrid()
end

-- simple hsv to rgb converter
function hsv2rgb(h, s, v)
    h = h / 60
    local c = s * v
    local x = c * (1 - math.abs(h % 2 - 1))
    if h < 1 then
        return {c, x, 0}
    elseif h < 2 then
        return {x, c, 0}
    elseif h < 3 then
        return {0, c, x}
    elseif h < 4 then
        return {0, x, c}
    elseif h < 5 then
        return {x, 0, c}
    else
        return {c, 0, x}
    end
end

-- function to create the initial sheet of particles and springs
function createGrid()
    -- clear any existing particles
    particles = {}
    sticks = {}
    local fixed, col
    local dx = 0
    for x = 100, 700, 10 do
        dx = 0
        -- set colour just based on x
        col = hsv2rgb(((x - 100) / 600) * 360, 0.8, 0.8)
        for y = 50, 90 do
            fixed = false
            -- fix a few particles in the top row
            if y == 50 and x % 50 == 0 then
                fixed = true
            end
            addParticle(x + dx, y, fixed, col)
            -- small dx so the particles fall slightly offest
            dx = dx + 1
            -- add spring above
            if y > 50 then
                addStick(particles[#particles], particles[#particles - 1], 10, true, col)
            end
            -- add spring to left
            if x > 100 then
                addStick(particles[#particles], particles[#particles - 41], 10, true, col)
            end
        end
    end
end

-- keypressed (f1 resets the cloth, f2 toggles the "fan", f3 toggles colour)
function love.keypressed(key)
    if key == "f1" then
        createGrid()
    end
    if key == "f2" then
        if fan ~= 0 then
            fan = 0
        else
            fan = math.random(50, 200)
        end
    end
    if key == "f3" then
        colourMode = not colourMode
    end
end

-- mousepressed, get a close particle and grab it
function love.mousepressed(x, y, button)
    if button == 1 then
        local dx, dy
        for i, p in ipairs(particles) do
            dx = math.abs(x - p.x)
            dy = math.abs(y - p.y)
            if dx < 6 and dy < 6 then
                p.fixed = true
                heldParticle = i
                break
            end
        end
    end
end

-- mouse released, drop a particle if we have one
function love.mousereleased(x, y, button)
    if button == 1 and heldParticle ~= -1 then
        particles[heldParticle].fixed = false
        heldParticle = -1
    end
end

-- mouse moved, set pos and old pos of held particle
function love.mousemoved(x, y)
    if heldParticle ~= -1 then
        particles[heldParticle].x = x
        particles[heldParticle].y = y
        particles[heldParticle].ox = x
        particles[heldParticle].oy = y
    end
end

function addStick(p1, p2, length, spring, col)
    local s = {p1 = p1, p2 = p2, length = length, spring = spring, col = col}
    table.insert(sticks, s)
    return s
end

function addParticle(x, y, fixed, col)
    local p = {x = x, y = y, ox = x, oy = y, fixed = fixed, col = col}
    table.insert(particles, p)
    return p
end

function updateParticle(p, dt)
    -- don't move fixed points
    if p.fixed then return end
    -- apply force from fan and gravity
    ax = fan
    ay = gravity
    -- calculate velocity based on last position and acceleration
    local vx = (p.x - p.ox) * (1 - drag) + ax * dt * dt
    local vy = (p.y - p.oy) * (1 - drag) + ay * dt * dt
    -- move
    p.ox, p.x = p.x, p.x + vx
    p.oy, p.y = p.y, p.y + vy
end

function particleMovePos(p, x, y)
    if p.fixed then return end
    p.x = p.x + x
    p.y = p.y + y
end

function checkBounds(p)
    -- check bounds
    if p.x > screenWidth then
        p.x = screenWidth
    elseif p.x < 0 then
        p.x = 0
    end
    if p.y > screenHeight then
        p.y = screenHeight
    elseif p.y < 0 then
        p.y = 0
    end
end

function updateStick(s)
    -- check distance between particles
    local dx = s.p1.x - s.p2.x
    local dy = s.p1.y - s.p2.y
    local dist = math.sqrt(dx * dx + dy * dy)
    -- work out difference and correction amount
    local diff = (s.length - dist) / dist  * 0.5
    -- springs only do anything if dist > length
    if s.spring and diff > 0 then return end
    -- apply to particles
    particleMovePos(s.p1, dx * diff, dy * diff)
    particleMovePos(s.p2, -dx * diff, -dy * diff)
end

function drawParticle(p)
    if colourMode then love.graphics.setColor(p.col) end
    love.graphics.circle("fill", p.x, p.y, 3)
end

function drawStick (s)
    if colourMode then love.graphics.setColor(s.col) end
    love.graphics.line(s.p1.x, s.p1.y, s.p2.x, s.p2.y)
end

function love.update(dt)
    -- if the fan is on, randomize it a bit
    if fan ~=0 then
        fan = fan + math.random(100) - 50
    end
    -- update particles
    for _, p in ipairs(particles) do
        updateParticle(p, dt)
    end
    -- update sticks (do multiple times to make smoother)
    for i = 1, 4 do
        for _, s in ipairs(sticks) do
            updateStick(s)
        end
    end
    -- check bounds
    for _, p in ipairs(particles) do
        checkBounds(p)
    end
end

function love.draw()
    love.graphics.setColor(1, 1, 1)
    -- draw particles
    for _, p in ipairs(particles) do
        drawParticle(p)
    end
    -- draw sticks
    for _, s in ipairs(sticks) do
        drawStick(s)
    end
end
User avatar
darkfrei
Party member
Posts: 1204
Joined: Sat Feb 08, 2020 11:09 pm

Re: Code Doodles!

Post by darkfrei »

First version of patchwork algorithm, it has gabs, but still good for using:
Communique Quilts on pinterest: https://www.pinterest.de/search/pins/?q ... s&rs=typed

Code: Select all

love.window.setMode(1280, 800)

local function updatePatches ()

	local index = math.random(#queue)
	local point = table.remove (queue, index)
	local x = point.x
	local y = point.y
--	local w = math.random (2, 5)
	local w = 2
	local h = math.random (2, 5)
	local lastW = false
	if x+w > gridW then
		w = gridW - x
		lastW = true
	end
	local lastH = false
	if y+h > gridH then 
		h = gridH - y
		lastH = true
	end

	for i, r in ipairs (rectangles) do
		if x < r.x+r.w 
		and r.x < x + w 
		and y < r.y+r.h 
		and r.y < y + h then
			if r.x + r.w > x then
				print ('w=0', x, y, w, h, r.x, r.y, r.w, r.h)
				w = 0
			elseif x + w > r.x then
				w = r.x-x
				print ('new w:', w)
			end
			if r.y + r.h > y then
				print ('h=0', x, y, w, h, r.x, r.y, r.w, r.h)
				h = 0
			elseif y + h > r.y then
				h = r.y-y
				print ('new h:', h)
			end
		end
		if w <= 0 or h <= 0 then
			break
		end
	end

	if (w > 0) and (h > 0) then
		if lastW and lastH then
			-- do nothing
		elseif lastW then
			table.insert (queue, {x=x, y=y+h})
		elseif lastH then
			table.insert (queue, {x=x+w, y=y})
		else
			table.insert (queue, {x=x, y=y+h})
			table.insert (queue, {x=x+w, y=y})
--			table.insert (queue, {x=x+w, y=y+h})
		end
		table.insert (rectangles, {x=x, y=y, w=w, h=h})
	end

end



function love.load()
	queue = {{x=0,y=0}}
	gridW, gridH, size = 32, 20, 40
	rectangles = {}
	step = 0.5
	time = 0
	pause = false
end


function love.update(dt)
	if not pause then
		time = time + dt
		while time > step do
			time = time-step
			if #queue > 0 then
				updatePatches ()
			end
		end
	end
end


function love.draw()
	for i, r in ipairs (rectangles) do
		love.graphics.setColor (1,1,1)
		love.graphics.rectangle ('fill', r.x*size, r.y*size, r.w*size, r.h*size)
		love.graphics.setColor (0,0,0)
		love.graphics.rectangle ('line', r.x*size, r.y*size, r.w*size, r.h*size)
		love.graphics.print (r.x..' '..r.y..' '..r.w..' '..r.h, r.x*size, r.y*size)

	end

	love.graphics.setColor (0,1,0)
	for i, p in ipairs (queue) do
		love.graphics.circle ('line', p.x*size, p.y*size, 4)
	end
end
2024-03-09T22_14_57-Untitled.png
2024-03-09T22_14_57-Untitled.png (46.58 KiB) Viewed 6613 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1204
Joined: Sat Feb 08, 2020 11:09 pm

Re: Code Doodles!

Post by darkfrei »

Communique Quilts - https://www.google.com/search?q=Communi ... s&tbm=isch
Examples to usage:
city map;
levels, that are more than one screen wide and/or more than one screen tall.

Code: Select all

love.window.setMode(1280, 800) -- Steam Deck resolution



local function updatePatches ()

	local index = math.random(#queue)
--		print ('index', index)
	local point = table.remove (queue, index)

	local rnew = {x=point.x,y=point.y}
	
	rnew.w = math.random (3, 5)
	rnew.h = math.random (3, 5)
	

	local lastW = false
	if rnew.x+rnew.w > gridW then
		rnew.w = gridW - rnew.x
--		lastW = true
	end
	local lastH = false
	if rnew.y+rnew.h > gridH then 
		rnew.h = gridH - rnew.y
--		lastH = true
	end

	local cols = {}

	for i, r in ipairs (rectangles) do
		if r.x+r.w > rnew.x and r.y+r.h > rnew.y 
		and rnew.x + rnew.w > r.x and rnew.y + rnew.h > r.y then
			table.insert (cols, r)
		end
	end

	for i, r in ipairs (cols) do
		if r.x > rnew.x then
			if rnew.x + rnew.w > r.x then
				rnew.w = r.x - rnew.x
			end
		elseif r.y > rnew.y then
			-- exception: do nothing
		else
			rnew.w = 0
		end
		if r.y > rnew.y then
			if rnew.y + rnew.h > r.y then
				rnew.h = r.y - rnew.y
			end
		elseif r.x > rnew.x then
			-- do nothing
		else
			rnew.h = 0
		end
	end

	if (rnew.w > 0) and (rnew.h > 0) then
		if lastW and lastH then
			-- do nothing
		elseif lastW then
			table.insert (queue, {x=rnew.x, y=rnew.y+rnew.h})
		elseif lastH then
			table.insert (queue, {x=rnew.x+rnew.w, y=rnew.y})
		else
			table.insert (queue, {x=rnew.x, y=rnew.y+rnew.h})
			table.insert (queue, {x=rnew.x+rnew.w, y=rnew.y})
--			table.insert (queue, {x=rnew.x+rnew.w, y=rnew.y+rnew.h})
		end
		table.insert (rectangles, rnew)
		return true
	end
end



function love.load()
--	math.randomseed( 10 )
	queue = {{x=0,y=0}}
	gridW, gridH, size = 32*2, 20*2, 40/2

	rectangles = {}
	step = 0.05
	time = 0
	pause = false
end


function love.update(dt)
	if not pause then
		time = time + dt
		while time > step do
			time = time-step
			if #queue > 0 then
				if not updatePatches () then
					time = time+step
				end
				love.window.setTitle ('queue: '.. #queue)
			end
		end
	end
end


function love.draw()
	for i, r in ipairs (rectangles) do
		love.graphics.setColor (1,1,1,0.75)
		love.graphics.rectangle ('fill', r.x*size, r.y*size, r.w*size, r.h*size)
		love.graphics.setColor (0,0,0)
		love.graphics.rectangle ('line', r.x*size, r.y*size, r.w*size, r.h*size)
	end

	love.graphics.setColor (0,1,0)
	for i, p in ipairs (queue) do
		love.graphics.circle ('line', p.x*size, p.y*size, 4)
	end
end

function love.keypressed(key, scancode, isrepeat)
	if key == "space" then
		pause = not pause
	elseif key == "escape" then
		love.event.quit()
	end
end
patchwork-communique-quilts-01
patchwork-communique-quilts-01
2024-03-10T18_26_17-queue_ 0.png (40.77 KiB) Viewed 6583 times
Attachments
patchwork-communique-quilts-01.love
(1.67 KiB) Downloaded 139 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Trystan
Prole
Posts: 15
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

This next one is some anemones trying to grab some balls (plankton maybe) using inverse kinematics.
IK.png
IK.png (195.44 KiB) Viewed 6489 times

Code: Select all

-- Disable output buffer so debug messages print in real time
io.stdout:setvbuf("no")

function love.load()
    bodies = {}
    balls = {}
    -- spawn some balls (targets)
    for i = 1, 20 do
        table.insert(balls, {x = math.random(love.graphics.getWidth()), y = math.random(love.graphics.getHeight()), spd = 2, dir = math.random(6)})
    end
    local segNum = 25 -- number of segments per body
    local segLen = 10 -- length of each segment
    local h = 0 -- current hue
    local numBods = 140 -- number of bodies, just used for colour
    -- spawn bodies, probably a better way to do this but these 4 loops get spots around the edge of the screen nicely
    -- top
    for i = 1, love.graphics.getWidth(), 20 do
        bodyCreate(i, 0, segNum, segLen, hsv2rgb(h, 0.8, 0.8))
        h = h + (360/numBods)
    end
    -- right
    for i = 1, love.graphics.getHeight(), 20 do
        bodyCreate(love.graphics.getWidth(), i, segNum, segLen, hsv2rgb(h, 0.8, 0.8))
        h = h + (360/numBods)
    end
    -- bottom
    for i = love.graphics.getWidth(), 1, -20 do
        bodyCreate(i, love.graphics.getHeight(), segNum, segLen, hsv2rgb(h, 0.8, 0.8))
        h = h + (360/numBods)
    end
    -- left
    for i = love.graphics.getHeight(), 1, -20 do
        bodyCreate(0, i, segNum, segLen, hsv2rgb(h, 0.8, 0.8))
        h = h + (360/numBods)
    end
end

-- get nearest target
function getTarget(b)
    local bestDist = math.huge
    local tx, ty, dist
    -- use the first segment for distance, looks slightly better than using the last
    local bx = b[1].ax
    local by = b[1].ay
    for _, t in ipairs(balls) do
        -- just get the squared distance, we don't need to know the actual distance
        dist = (bx - t.x) * (bx - t.x) + (by - t.y) * (by - t.y)
        if dist < bestDist then
            tx = t.x
            ty = t.y
            bestDist = dist
        end
    end
    return tx, ty
end

function love.update(dt)
    -- update balls
    for _, b in ipairs(balls) do
        -- give them a random heading change (within a small arc)
        b.dir = b.dir + (math.random() - 0.5) / 2
        -- move
        b.x = b.x + math.cos(b.dir) * b.spd
        b.y = b.y - math.sin(b.dir) * b.spd
        -- flip angle if they hit an edge
        if b.x < 0 or b.x > love.graphics.getWidth() or b.y < 0 or b.y > love.graphics.getHeight() then
            b.dir = b.dir + math.pi
        end
    end
    -- update bodies
    local tx, ty
    for _, b in ipairs(bodies) do
        -- set target as nearest ball
        tx, ty = getTarget(b)
        -- update
        bodyUpdate(b, tx, ty)
    end
end

-- simple hsv to rgb converter
function hsv2rgb(h, s, v)
    h = h / 60
    local c = s * v
    local x = c * (1 - math.abs(h % 2 - 1))
    if h < 1 then
        return {c, x, 0}
    elseif h < 2 then
        return {x, c, 0}
    elseif h < 3 then
        return {0, c, x}
    elseif h < 4 then
        return {0, x, c}
    elseif h < 5 then
        return {x, 0, c}
    else
        return {c, 0, x}
    end
end

-- draw a body
function bodyShow(b)
    -- set colour
    love.graphics.setColor(b.col)
    -- draw segments
    for _, s in ipairs(b) do
        bx, by = segGetB(s)
        love.graphics.line(s.ax, s.ay, bx, by)
    end
end

function bodyUpdate(b, tx, ty)
    -- position segments to grab target, starting with the last segment
    for i = #b, 1, -1 do
        -- get angle to target
        b[i].angle = math.atan2(b[i].ay - ty, tx - b[i].ax)
        -- set start position so end is at target
        b[i].ax = tx - math.cos(b[i].angle) * b[i].len
        b[i].ay = ty + math.sin(b[i].angle) * b[i].len
        -- set our start position as target for the previous segment
        tx = b[i].ax
        ty = b[i].ay
    end
    -- anchor start, first work out difference from first segments start to the anchor point
    local dx = b[1].ax - b[1].ox
    local dy = b[1].ay - b[1].oy
    -- apply that difference to all segments to anchor
    for i = 1, #b do
        b[i].ax = b[i].ax - dx
        b[i].ay = b[i].ay - dy
    end
end

-- create a body anchored at x, y with segNum segments of segLen and colour col
function bodyCreate(x, y, segNum, segLen, col)
    local body = {}
    body.col = col
    -- set initial x, y
    local ax, ay = x, y
    for i = 1, segNum do
        body[i] = {}
        -- if it's the first segment set the anchor point
        if i == i then
            body[i].ox = ax
            body[i].oy = ay
        end
        body[i].ax = ax
        body[i].ay = ay
        body[i].angle = 0
        body[i].len = segLen
        -- set start x, y for the next segment to this segment's end x, y
        ax, ay = segGetB(body[i])
    end
    table.insert(bodies, body)
end

-- gets the end of a segment from the start and it's angle
function segGetB(b)
    local bx = b.ax + math.cos(b.angle) * b.len
    local by = b.ay - math.sin(b.angle) * b.len
    return bx, by
end

function love.draw()
    -- draw bodies
    for _, b in ipairs(bodies) do
        bodyShow(b)
    end
    -- draw balls
    love.graphics.setColor(1, 1, 1)
    for _, b in ipairs(balls) do
        love.graphics.circle("fill", b.x, b.y, 10)
    end
end
User avatar
pgimeno
Party member
Posts: 3672
Joined: Sun Oct 18, 2015 2:58 pm

Re: Code Doodles!

Post by pgimeno »

That's mesmerizing! Very nice.
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: Code Doodles!

Post by RNavega »

roundedRectanglePreview.gif
roundedRectanglePreview.gif (169.05 KiB) Viewed 6399 times

Example of drawing a round-corner rectangle using a pixel shader.
While there's the rx,ry parameters of love.graphics.rectangle() that make a rounded rectangle using geometry, in this way it's done by fading the alpha of pixels outside of the rounded corners.

Code: Select all

io.stdout:setvbuf('no')

local rect = {
    x1 = 80.0,         y1 = 140.0,
    x2 = 80.0 + 400.0, y2 = 140.0 + 160.0,
    borderData = {
        -- Width (px)
        nil,
        -- Height (px)
        nil,
        -- Initial corner radius (px)
        50.0,
        -- Initial border thickness (px)
        12.0
    }
}
local mesh
local shader

local PIXEL_SOURCE = [[
uniform vec4 border_data;

vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
    // The default Löve shader code, unused:

    //vec4 texcolor = Texel(tex, texture_coords);
    //return texcolor * color;

    // --------------------------------------------------------------

    // The rounded rectangle & border code:

    const vec4 WHITE_COLOR = vec4(1.0);

    vec2 local_pixel = texture_coords * border_data.xy;

    vec2 center_pixel = border_data.xy / 2.0;

    vec2 center_offset = abs(local_pixel - center_pixel);
    vec2 corner_offset = center_offset - (center_pixel - border_data.z);
    vec2 is_corner = step(0.0, corner_offset);

    float corner_offset_length = length(corner_offset);
    // This +0.5 constant seems to give the smoothest result.
    float corner_antialias = corner_offset_length + 0.5 - border_data.z;

    vec2 border_offset = center_offset - (center_pixel - border_data.w);
    vec2 is_edge_border = step(0.0, border_offset);
    float border_antialias = corner_offset_length + 0.5 - (border_data.z - border_data.w);
    float is_corner_border = (border_antialias * is_corner.x * is_corner.y);
    float border_factor = clamp(is_edge_border.x + is_edge_border.y + is_corner_border, 0.0, 1.0);
    vec4 final_color = mix(color, WHITE_COLOR, border_factor);

    final_color.a = 1.0 - (corner_antialias * is_corner.x * is_corner.y);
    return final_color;
}
]]

-- Cosmetic, used for dragging the corners.
local draggedCorner = nil
local highlightedCorner = nil
local dragOffset = {0.0, 0.0}
local cornerKeys = {{'x1', 'y1'}, {'x2', 'y1'}, {'x2', 'y2'}, {'x1', 'y2'}}


function love.load()
    mesh = love.graphics.newMesh(4, 'fan', 'static')
    -- Default format: x, y, u, v, r, g, b, a.
    -- Setting only x, y, u, v.
    -- Creating the mesh sized at 1 x 1 px, to be scaled to its proper width & height
    -- using the 'sx' and 'sy' parameters of love.graphics.draw().
    mesh:setVertices({
        {0.0, 0.0, 0.0, 0.0},
        {1.0, 0.0, 1.0, 0.0},
        {1.0, 1.0, 1.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
    })
    -- TODO: it's also possible to use a custom mesh format, and then setup the
    -- "borderData" information per vertex, so that you can have many rounded
    -- rectangles of different sizes and borders, all stored in the same mesh.
    mesh:setVertexMap(1, 2, 3, 4)
    shader = love.graphics.newShader(PIXEL_SOURCE)
    love.keyboard.setKeyRepeat(true)
end


function love.draw()
    love.graphics.setShader(shader)
    love.graphics.setColor(0.5, 0.5, 0.5)
    rect.borderData[1] = rect.x2 - rect.x1
    rect.borderData[2] = rect.y2 - rect.y1
    shader:send('border_data', rect.borderData)
    love.graphics.draw(mesh, rect.x1, rect.y1, 0.0, rect.borderData[1], rect.borderData[2])

    love.graphics.setShader()

    if highlightedCorner then
        local keys = cornerKeys[highlightedCorner]
        love.graphics.setColor(0.0, 1.0, 1.0, 0.333)
        local x = rect[keys[1]]
        local y = rect[keys[2]]
        local tolerance = rect.borderData[3] > 32 and rect.borderData[3] or 32
        if highlightedCorner == 2 or highlightedCorner == 3 then
            x = x - tolerance
        end
        if highlightedCorner == 3 or highlightedCorner == 4 then
            y = y - tolerance
        end
        love.graphics.rectangle('fill', x, y, tolerance, tolerance)
    end
    love.graphics.setColor(1.0, 1.0, 1.0)
    local info = string.format('Drag the rectangle corners to resize it (%d x %d px).\n\n'
                             ..'Press and hold Up / Down to resize the corner radius (%d px).\n\n'
                             ..'Press and hold Left / Right to resize the border thickness (%d px).\n\n'
                             ..'Press Esc to quit.',
                               rect.x2 - rect.x1,
                               rect.y2 - rect.y1,
                               rect.borderData[3],
                               rect.borderData[4])
    love.graphics.print(info, 10, 10)
end


function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    elseif key == 'up' then
        rect.borderData[3] = (rect.borderData[3] > 0.0 and rect.borderData[3] - 1.0 or 0.0)
    elseif key == 'down' then
        local halfHeight = math.abs(rect.y2 - rect.y1) / 2.0
        rect.borderData[3] = (rect.borderData[3] < halfHeight and rect.borderData[3] + 1.0
                              or halfHeight)
    elseif key == 'left' then
        rect.borderData[4] = (rect.borderData[4] > 0.0 and rect.borderData[4] - 1.0 or 0.0)
    elseif key == 'right' then
        local halfHeight = math.abs(rect.y2 - rect.y1) / 2.0
        rect.borderData[4] = (rect.borderData[4] < halfHeight and rect.borderData[4] + 1.0
                              or halfHeight)
    end
end


function love.mousemoved(x, y, dx, dy)
    if draggedCorner then
        local keys = cornerKeys[draggedCorner]
        rect[keys[1]] = dragOffset[1] + x
        rect[keys[2]] = dragOffset[2] + y
    else
        highlightedCorner = findCorner(x, y)
    end
end


function love.mousepressed(x, y, button)
    local corner = findCorner(x, y)
    if corner then
        draggedCorner = corner
        local keys = cornerKeys[corner]
        dragOffset[1] = rect[keys[1]] - x
        dragOffset[2] = rect[keys[2]] - y
    end
end


function love.mousereleased(x, y)
    draggedCorner = nil
    highlightedCorner = findCorner(x, y)
end


function findCorner(x, y)
    local tolerance = rect.borderData[3] > 32 and rect.borderData[3] or 32
    local leftEdge   = x >= rect.x1 and x <= rect.x1 + tolerance
    local rightEdge  = x >= rect.x2 - tolerance and x <= rect.x2
    local topEdge    = y >= rect.y1 and y <= rect.y1 + tolerance
    local bottomEdge = y >= rect.y2 - tolerance and y <= rect.y2
    if leftEdge and topEdge then
        return 1
    elseif rightEdge and topEdge then
        return 2
    elseif rightEdge and bottomEdge then
        return 3
    elseif leftEdge and bottomEdge then
        return 4
    end
    return nil
end
User avatar
veethree
Inner party member
Posts: 877
Joined: Sat Dec 10, 2011 7:18 pm

Re: Code Doodles!

Post by veethree »



A simple genetic evolution simulation.

Each dot has a set of "genes" which are just a table of numbers that determine how the dots move and what color they are. They also affect a dots breeding range and some other stuff i forgot probably

When 2 dots get close to each other, And they're old and lucky enough, They have a chance of breeding and producing an offspring that will get random genes from each parent. There is also a chance of mutation so the gene pool retains some variation.

Overpopulation is also simulated. If a dot has too many other dots around it it will die faster.

They will also die faster due to loneliness if they have no friends around them

Over time they will form little racist communities based on their genes. Just like people.
genes.love
(12.57 KiB) Downloaded 126 times
User avatar
pgimeno
Party member
Posts: 3672
Joined: Sun Oct 18, 2015 2:58 pm

Re: Code Doodles!

Post by pgimeno »

When I saw this simulation: https://myphysicslab.com/engine2D/pendu ... ck-en.html I couldn't resist giving it a try with love and box2D. Here's the result:

A still frame of the simulation
A still frame of the simulation
pendulum.png (8.32 KiB) Viewed 4882 times

Code: Select all

local lg = love.graphics
local lp = love.physics

local ballRadius = 40
local wheelRadius = 120
local fangDistance = 42
local fangHeight = 27
local torque = 80000


lp.setMeter(100)
local world = lp.newWorld(0, 980)

-- Invisible "holder" that holds revolute joints
local holder = lp.newBody(world, 400, 300, "static")

local wheel = {
  body = lp.newBody(world, 400, 400, "dynamic")
}
wheel.body:setAngularVelocity(1)
wheel.joint = lp.newRevoluteJoint(holder, wheel.body, 400, 400)

local transform = love.math.newTransform(0, 0)
for i = 0, 23 do
  transform:setTransformation(0, 0, 2*3.1415926535897932384/24*i)
  local x1, y1 = transform:transformPoint(0, wheelRadius * 1.1)
  local x2, y2 = transform:transformPoint(0, wheelRadius * 0.7)
  local x3, y3 = transform:transformPoint(wheelRadius * 0.22, wheelRadius * 0.7)
  local shape = lp.newPolygonShape(x1, y1, x2, y2, x3, y3)
  local fixture = lp.newFixture(wheel.body, shape)
  fixture:setMask(1) -- don't collide with others in the default category 1
  table.insert(wheel, fixture)
end

local pendulum = {
  body = lp.newBody(world, 400, 250, "dynamic")
}
pendulum.joint = lp.newRevoluteJoint(holder, pendulum.body, 400, 250)

pendulum.ball = lp.newFixture(pendulum.body, lp.newCircleShape(0, 250, ballRadius))
pendulum.shaft = lp.newFixture(pendulum.body, lp.newPolygonShape(-5, 0, 5, 0, 5, 250, -5, 250))

pendulum.anchorHolder = lp.newFixture(pendulum.body, lp.newPolygonShape(-fangDistance-5, -5, fangDistance+5, -5, fangDistance+5, 5, -fangDistance-5, 5))

pendulum.fang1 = lp.newFixture(pendulum.body, lp.newPolygonShape(-fangDistance-20, 0, -fangDistance, 0, -fangDistance, fangHeight))
pendulum.fang1:setCategory(2)

pendulum.fang2 = lp.newFixture(pendulum.body, lp.newPolygonShape(fangDistance, 0, fangDistance+20, 0, fangDistance, fangHeight))
pendulum.fang2:setCategory(2)

--pendulum.body:setAngle(math.rad(10))

function love.keypressed(k, s)
  if s == "escape" then return love.event.quit() end
end

function love.update(dt)
  wheel.body:applyTorque(torque)
  world:update(dt)
end

function love.draw()
  lg.setColor(0.9, 0.7, 0.7, 0.5)
  for i = 1, 24 do
    lg.polygon("fill", wheel.body:getWorldPoints(wheel[i]:getShape():getPoints()))
  end
  lg.setColor(0.9, 0.9, 0.9, 0.7)
  lg.polygon("fill", pendulum.body:getWorldPoints(pendulum.fang1:getShape():getPoints()))
  lg.polygon("fill", pendulum.body:getWorldPoints(pendulum.fang2:getShape():getPoints()))
  lg.polygon("fill", pendulum.body:getWorldPoints(pendulum.shaft:getShape():getPoints()))
  lg.polygon("fill", pendulum.body:getWorldPoints(pendulum.anchorHolder:getShape():getPoints()))
  local ballX, ballY = pendulum.body:getWorldPoints(pendulum.ball:getShape():getPoint())
  lg.circle("fill", ballX, ballY, ballRadius)
  lg.setColor(0, 0, 0, 0.8)
  lg.circle("fill", 400, 250, 4)
end
Post Reply

Who is online

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