## Code Doodles!

General discussion about LÖVE, Lua, game development, puns, and unicorns.
PGUp
Party member
Posts: 105
Joined: Fri Apr 21, 2017 9:17 am

### Re: Code Doodles!

frac.png (26.3 KiB) Viewed 13817 times
generating fractal, one pixel at a time

Code: Select all

function getDist(x1, y1, x2, y2)
return math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2))
end

function getPos(x, y, rot, dist)
return x - math.sin(rot) * dist, y + math.cos(rot) * dist
end

function getAngle(x1, y1, x2, y2)
return math.atan2(x1 - x2, y2 - y1)
end

math.randomseed(os.time())
lg = love.graphics
lw = love.window
lw.setMode(500, 500)
poly = {}
for i=1, 3, 1 do
local p = {}
table.insert(poly, p)
end
points = {}
local r1 = math.random()
local r2 = math.random()
local p = {}
p.x = (1 - math.sqrt(r1)) * poly[1].x + (math.sqrt(r1) * (1 - r2)) * poly[2].x + (math.sqrt(r1) * r2) * poly[3].x
p.y = (1 - math.sqrt(r1)) * poly[1].y + (math.sqrt(r1) * (1 - r2)) * poly[2].y + (math.sqrt(r1) * r2) * poly[3].y
table.insert(points, p)
--MAX POINTS
spawn = 10000
end

function love.update()
if spawn > 0 then
--MODIFY THIS FOR LOOP TO CHANGE THE SPEED OF THE GENERATOR
for i=1, 10, 1 do
local p = {}
local pick = math.random(1, 3)
local distance = getDist(points[#points].x, points[#points].y, poly[pick].x, poly[pick].y)
local angle = getAngle(points[#points].x, points[#points].y, poly[pick].x, poly[pick].y)
p.x, p.y = getPos(points[#points].x, points[#points].y, angle, distance/2)
table.insert(points, p)
spawn = spawn - 1
end
end
end

function love.draw()
for i,v in pairs(poly) do
lg.setColor(1,1,1)
lg.circle("fill", v.x, v.y, 5)
end
lg.setColor(1,0,0)
for i,v in pairs(points) do
lg.circle("fill", v.x, v.y, 1)
end
end

-

rmcode
Party member
Posts: 454
Joined: Tue Jul 15, 2014 12:04 pm
Location: Germany
Contact:

### Re: Code Doodles!

veethree
Inner party member
Posts: 820
Joined: Sat Dec 10, 2011 7:18 pm

### Re: Code Doodles!

Wrote a function that extracts colors from an image. It does this in a very naive way so it only works good sometimes.
1541699302.png (432.87 KiB) Viewed 13038 times
The attachment 1541699312.png is no longer available
The attachment 1541699302.png is no longer available

Code: Select all

function love.load()
http = require("socket.http")
s = ""
love.window.setMode(640, 480)
love.graphics.setLineStyle("rough")

local img = getImage()
image = love.graphics.newImage(img)

color = scanImage(img)
sprint("Total pixels: "..(640 * 480))
sprint("Extracted colors: "..#color)
sprint("Hold space to show colors")
end

function sprint(t)
s = s..t.."\n"
end

function scanImage(data, skip, distance)
skip = skip or 10
distance = distance or 1

local colors = {}
for y=0, data:getHeight()-1, skip do
for x=0, data:getWidth()-1, skip do
local r, g, b, a = data:getPixel(x, y)

for i,v in ipairs(colors) do
if colorDistance(v, {r, g, b, a}) < distance then
end
end

colors[#colors + 1] = {r, g, b, a}
end
end
end

return colors
end

function love.update(dt)

end

function love.draw()
if image then love.graphics.draw(image) end

for i,v in ipairs(color) do
love.graphics.setColor(v)
love.graphics.rectangle("fill", (love.graphics.getWidth() / #color) * (i - 1), love.graphics.getHeight() - 64, love.graphics.getWidth() / #color, 64)
end

love.graphics.setColor(1, 1, 1)
love.graphics.print(s, 12, 12)
end

function love.keypressed(key)
if key == "escape" then love.event.push("quit") end
if key == "r" then
end

if key == "1" then
love.graphics.captureScreenshot(os.time()..".png")
end
end

function colorDistance(a, b)
return math.abs(a[1] - b[1]) + math.abs(a[2] - b[2]) + math.abs(a[3] - b[3])
end

function getImage()
local body, code = http.request("https://picsum.photos/640/480/?random")
local data = false
if body then
love.filesystem.write("tmp.jpg", body)
data = love.image.newImageData("tmp.jpg")
love.filesystem.remove("tmp.jpg")
else
error(code)
end

return data
end

pgimeno
Party member
Posts: 2211
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Code Doodles!

I made this to visualize the bias in formulas for picking random points from the surface of a unit sphere, using four different methods. It rotates the sphere (in orthographic projection) so it can be seen from several angles. It looked nice so I thought I'd add it to this thread too:

Code: Select all

local rng
local sin, cos, sqrt, pi = math.sin, math.cos, math.sqrt, math.pi

local tf = love.math.newTransform()
local tf2 = love.math.newTransform()
local pts = {}

local npts = 80000

local function randvec3d_1()
local x = rng:random() * 2 * pi
local y = rng:random() * 2 * pi
local z = sin(y)
return { x = cos(x) * z, y = sin(x) * z, z = cos(y) }
end

local function randvec3d_2()
local x = rng:random() * 2 - 1
local y = rng:random() * 2 - 1
local z = rng:random() * 2 - 1
local r = sqrt(x*x+y*y+z*z)
return {x = x/r, y = y/r, z = z/r}
end

local function randvec3d_3()
local x, y, z, r
repeat
x = rng:random() * 2 - 1
y = rng:random() * 2 - 1
z = rng:random() * 2 - 1
r = sqrt(x*x+y*y+z*z)
until r >= 0.0001 and r < 1
return {x = x/r, y = y/r, z = z/r}
end

local function randvec3d_4()
local z = rng:random()*2 - 1
local a = rng:random()*(2*pi)
local r = sqrt(1 - z*z)
return { x = r*cos(a), y = r*sin(a), z = z }
end

local randvec_3d = randvec3d_1

local function regen()
local len = 0
for i = 1, npts do
v = randvec_3d()
len = len + 3
pts[len - 2] = v.x
pts[len - 1] = v.y
pts[len] = v.z
end
end

rng = love.math.newRandomGenerator(love.timer.getTime()*1000000)
regen()
end

local pt2d = {}

function love.update(dt)
for i = 1, npts do
local x, y, z = pts[i*3 - 2], tf:transformPoint(pts[i*3 - 1], pts[i*3])
pt2d[i*2 - 1], pt2d[i*2] = tf2:transformPoint(x, y), z
end
tf:rotate(dt/pi)
tf2:rotate(dt/3)
end

function love.draw()
love.graphics.translate(400, 300)
love.graphics.scale(290, 290)
love.graphics.points(pt2d)
end

function love.keypressed(_, k)
if k == "escape" then return love.event.quit() end
if k == "1" then randvec_3d = randvec3d_1 regen() end
if k == "2" then randvec_3d = randvec3d_2 regen() end
if k == "3" then randvec_3d = randvec3d_3 regen() end
if k == "4" then randvec_3d = randvec3d_4 regen() end
end

The method is chosen with the numbers 1 to 4 in the regular keyboard.

Method 1 picks two random angles and uses them as the spherical coordinates of a point in the sphere. This results in a clear clustering near the poles:

Method 2 picks three random numbers in [-1, 1] and normalizes the resulting vector. This causes clustering in the projection of the edges and vertices of the cube that circumscribes the sphere:

Method 3 is like method 2, but if the length of the vector is > 1 or < 0.0001, it's rejected and another one is picked until one within range is found. The time to obtain a valid vector is unbound, but in practice it doesn't take too many iterations to get a valid one. This results in a good uniform distribution no matter the angle:

Method 4 picks a random point in the curved side of a cylinder with radius 1 that circumscribes the sphere, and then projects the cylinder on the sphere horizontally. This results in a uniform distribution too, essentially equal to method 3, so I haven't taken a snapshot for it.

We can modify the code to prevent projecting the cylinder, by forcing the radius to 1:

Code: Select all

--  local r = sqrt(1 - z*z)
local r = 1

and this is the result:

I chose the number of points (npts) so that they can be calculated in a single frame in my computer; you may need to adjust that depending on the required frame rate and how fast your computer is.
Attachments
random-vector-visualize.love

veethree
Inner party member
Posts: 820
Joined: Sat Dec 10, 2011 7:18 pm

### Re: Code Doodles!

This one isn't contained in main.lua, But i'd consider it a doodle. It's a bunch of randomly moving balls who evolve the ability to hit a target and avoid an obstacle via a genetic algorithm.

It's pretty heavily inspired by this tutorial.

The code is pretty haphazardly thrown together, But it works.
Genetic.love
ga1.png (53.29 KiB) Viewed 11410 times
Attachments
ga2.png (52.65 KiB) Viewed 11410 times

veethree
Inner party member
Posts: 820
Joined: Sat Dec 10, 2011 7:18 pm

### Re: Code Doodles!

GOO SIMULATOR 2020

You need moonshine for it to work cause i'm bad at shaders.
1560203306.png (10.56 KiB) Viewed 9427 times

Code: Select all

function love.load()
s = ""
--love.graphics.setBackgroundColor(1, 1, 1)
love.physics.setMeter(40)
world = love.physics.newWorld(0, love.physics:getMeter() * 8, true)

moonshine = require 'moonshine'

blur = moonshine(moonshine.effects.gaussianblur)
blur.gaussianblur.sigma = 6

vec4 effect( vec4 color, Image tex, vec2 tc, vec2 sc )
{
vec4 pixel = Texel(tex, tc);
number avg = (pixel.r + pixel.g + pixel.b) / 3;
if (avg > 0.01) {
return vec4(1.0, 1.0, 1.0, 1.0) * color;
} else {
return pixel;
}
}
]])

balls = {}
count = 500
ballSize = 2
sizeVariation = 2

for i=1, count do
balls[i] = {}
balls[i].body = love.physics.newBody(world, love.graphics.getWidth() / 2 + math.random(), -(ballSize * 2) * i, "dynamic")
balls[i].body:setMass(0.1)
balls[i].size = ballSize + (sizeVariation * math.random())
balls[i].shape = love.physics.newCircleShape(balls[i].size)
balls[i].fixture = love.physics.newFixture(balls[i].body, balls[i].shape, 1)
balls[i].fixture:setRestitution(0.3)
balls[i].fixture:setFriction(0.1)
end

walls = {}
walls[1] = {}
walls[1].body = love.physics.newBody(world, love.graphics.getWidth() / 2, love.graphics.getHeight() - 5, "static")
walls[1].shape = love.physics.newRectangleShape(love.graphics.getWidth(), 10)
walls[1].fixture = love.physics.newFixture(walls[1].body, walls[1].shape, 1)

walls[2] = {}
walls[2].body = love.physics.newBody(world, 0, love.graphics.getHeight() / 2, "static")
walls[2].shape = love.physics.newRectangleShape(10, love.graphics.getWidth())
walls[2].fixture = love.physics.newFixture(walls[2].body, walls[2].shape, 1)

walls[3] = {}
walls[3].body = love.physics.newBody(world, love.graphics.getWidth() - 10, love.graphics.getHeight() / 2, "static")
walls[3].shape = love.physics.newRectangleShape(10, love.graphics.getWidth())
walls[3].fixture = love.physics.newFixture(walls[3].body, walls[3].shape, 1)

walls[4] = {}
walls[4].body = love.physics.newBody(world, love.graphics.getWidth() / 2, love.graphics.getHeight() / 2, "static")
walls[4].shape = love.physics.newRectangleShape(64, 16)
walls[4].fixture = love.physics.newFixture(walls[4].body, walls[4].shape, 1)

walls[5] = {}
walls[5].body = love.physics.newBody(world, love.graphics.getWidth() / 4, love.graphics.getHeight() / 1.5, "static")
walls[5].shape = love.physics.newRectangleShape(0, 0, 100, 8, math.pi / 4)
walls[5].fixture = love.physics.newFixture(walls[5].body, walls[5].shape, 1)

walls[6] = {}
walls[6].body = love.physics.newBody(world, love.graphics.getWidth() - (love.graphics.getWidth() / 4), love.graphics.getHeight() / 1.5, "static")
walls[6].shape = love.physics.newRectangleShape(0, 0, 100, 8, -(math.pi / 4))
walls[6].fixture = love.physics.newFixture(walls[6].body, walls[6].shape, 1)

player = {}
player.body = love.physics.newBody(world, love.graphics.getWidth() / 2, love.graphics.getHeight() / 2, "dynamic")
player.size = ballSize * 16
player.body:setMass(player.size * 8)
player.speed = 1000
player.maxVel = 200
player.jump = 30
player.shape = love.physics.newCircleShape(player.size)
player.fixture = love.physics.newFixture(player.body, player.shape, 1)
player.fixture:setRestitution(0.3 * math.random() + 0.2)

snow = love.graphics.newCanvas()

sprint("'SPACE' to reset")
sprint("'R' to make shit fly")
end

function sprint(t)
s = s..t.."\n"
end

function love.update(dt)
local mx, my = love.mouse.getPosition()

local xVel, yVel = player.body:getLinearVelocity()
local nx, ny = xVel, yVel
if love.keyboard.isDown("d") then
nx = nx + player.speed * dt
if nx > player.maxVel then
nx = player.maxVel
end
elseif love.keyboard.isDown("a") then
nx = nx - player.speed * dt
if nx < -player.maxVel then
nx = -player.maxVel
end
end

player.body:setLinearVelocity(nx, ny)

world:update(dt)
end

function love.draw()
love.graphics.setCanvas(snow)
love.graphics.clear()
blur(function()
love.graphics.setColor(1, 1, 1, 1)
for i,v in ipairs(balls) do
love.graphics.circle("fill", v.body:getX(), v.body:getY(), v.size)
end
end)
else
love.graphics.setColor(1, 1, 1, 1)
for i,v in ipairs(balls) do
love.graphics.circle("fill", v.body:getX(), v.body:getY(), v.size)
end
end
love.graphics.setCanvas()

love.graphics.setColor(0.2, 0.8, 0.4, 1)
love.graphics.draw(snow)

love.graphics.setColor(1, 1, 1, 1)
for i,v in ipairs(walls) do
love.graphics.polygon("fill", v.body:getWorldPoints(v.shape:getPoints()))
end

love.graphics.setColor(1, 0.6, 0.4, 1)
love.graphics.circle("fill", player.body:getX(), player.body:getY(), player.size)

love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(s, 12, 12, love.graphics.getWidth(), "left")
love.graphics.printf(love.timer.getFPS(), 0, 12, love.graphics.getWidth(), "center")
end

function love.keypressed(key)
if key == "escape" then
love.event.push("quit")
elseif key == "space" then
elseif key == "1" then
elseif key == "2" then
love.graphics.captureScreenshot(os.time()..".png")
--sprint("Screenshot captured.")
end

if key == "w" then
player.body:applyLinearImpulse(0, -(player.size * 18) )
end

if key == "r" then
for i,v in ipairs(balls) do
v.body:applyLinearImpulse(0, -10 * math.random())
end
end
end
Attachments
1560203302.png (12.35 KiB) Viewed 9427 times

pgimeno
Party member
Posts: 2211
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Code Doodles!

While I was watching this demo by Melon Dezign, the shadebobs effect caught my attention. I wondered if I would be able to replicate it with Löve to any degree of accuracy, and well, while it's not identical, I'm pretty satisfied with this result.

The first three effects use parameters that mimic those in the demo; the rest are random.

Here it is:

Code: Select all

-- Cache some stuff into locals
local love = love
local lg = love.graphics
local min = math.min
local max = math.max
local abs = math.abs
local sin = math.sin
local cos = math.cos
local pi = math.pi
local random = love.math.random

-- Colour scaler from 0..255 to 0..1
local cMult = 0.003922

-- Parameters
local decr = 12
local FXs = {
-- frequency 1, freq. 2, phase 1, phase 2, num vertices, angular speed,
-- initial angle, colour, total time
{f1 = 1.2, f2 = 1.4, ph1 = 9.4, ph2 = 3.6, n = 4, w = -3, a0 = pi/4,
c = {0, 0, 136*cMult}, t = 10},
{f1 = 0.7, f2 = 1.6, ph1 = 43.6, ph2 = 39.4, n = 5, w = -2.05, a0 = 0,
c = {255*cMult, 142*cMult, 7*cMult}, t = 10},
{f1 = 3, f2 = 5.7, ph1 = 45.8, ph2 = 2.2, n = 4, w = -3.1, a0 = pi/4,
c = {144*cMult, 144*cMult, 235*cMult}, t = 8},
}

-- Current effect
local fx = {}

-- Effect index
local idx = 0

-- Freeze frame
local frozen = false

-- Double buffer
local canvas, newCanvas

-- Take care of the width and height
local W, H, size

function love.resize(w, h)
if w * 0.75 < h then
-- Use height as reference
W = h / 0.75
H = h
else
-- Use width as reference
W = w
H = w * 0.75
end
local margin = H * 0.0625
W = W - margin
H = H - margin
size = H * 0.125
-- Margin
W = W - size*1.5
H = H - size*1.5

-- (re)Create the double buffer
if canvas then
canvas:release()
newCanvas:release()
end
canvas = lg.newCanvas()
newCanvas = lg.newCanvas()
end

love.resize(lg.getWidth(), lg.getHeight())

-- "Triangle" shader: Map the range 0..0.5 to 0..1 and the range 0.5..1 to 1..0
vec4 effect(vec4 col, Image tex, vec2 texpos, vec2 scrpos)
{
vec4 c = Texel(tex, texpos);
return (1. - abs(2.*c - 1.)) * col;
}
]]

uniform Image canvas;

vec4 effect(vec4 col, Image tex, vec2 texpos, vec2 scrpos)
{
return mod(Texel(tex, texpos) * col + Texel(canvas, scrpos/love_ScreenSize.xy), 1.);
}
]]

-- Angle to draw the polygon at
local angle = 0

-- The time for the Lissajous figure
local lissatime = 0

-- Prevents releasing objects
local cachedobj = {c = {}}

local function newBob()
lg.setCanvas(canvas)
lg.clear(0, 0, 0, 1)
lg.setCanvas()
idx = idx + 1
if idx < 1 then idx = 1 end
fx = FXs[idx]
if not fx then
fx = cachedobj
love.math.setRandomSeed(idx)
local colour = cachedobj.c
-- Select a colour at full saturation
local hue = random() * 6
colour[1] = min(max(abs(hue - 3) - 1, 0), 1)
hue = (hue + 2) % 6
colour[2] = min(max(abs(hue - 3) - 1, 0), 1)
hue = (hue + 2) % 6
colour[3] = min(max(abs(hue - 3) - 1, 0), 1)
-- Set saturation to a random value (increases lightness)
-- with high values more likely than low
local sat = random() ^ .2
colour[1] = 1 - (1 - colour[1]) * sat
colour[2] = 1 - (1 - colour[2]) * sat
colour[3] = 1 - (1 - colour[3]) * sat

fx.f1 = random()^2 * 5 + 0.5 -- prefer low values
fx.f2 = random()^2 * 5 + 0.5
fx.ph1 = random() * 40
fx.ph2 = random() * 40
fx.n = random(3, 6)
fx.w = random() * 8 - 4
fx.a0 = random() * (2 * pi)
fx.c = colour
fx.t = random() * 5 + 7
end

lissatime = 0
angle = fx.a0
end

newBob()
end

function love.update(dt)
if frozen then return end
local lt = lissatime
lissatime = lissatime + dt
if lissatime > fx.t + fadetime then newBob() end

local centreX = cos((lt + fx.ph1) * fx.f1) * (W * 0.5)
local centreY = cos((lt + fx.ph2) * fx.f2) * (H * 0.5)
-- Copy current canvas to new
lg.setCanvas(newCanvas)
lg.origin()
lg.setColor(1, 1, 1)
lg.setBlendMode("replace", "premultiplied")
lg.draw(canvas)

lg.translate(lg.getWidth()*0.5, lg.getHeight()*0.5)
lg.translate(centreX, centreY)
lg.rotate(angle)
local d = min(lissatime * 10, 1) * decr -- fixes first frame (??)
lg.setColor(d * cMult, d * cMult, d * cMult)
lg.circle("fill", 0, 0, size, fx.n)
lg.setCanvas()
angle = angle + fx.w * dt

-- Flip buffers
canvas, newCanvas = newCanvas, canvas
end

function love.draw()
-- Draw the current canvas
if lissatime >= fx.t then
local r = fx.c[1] * a
local g = fx.c[2] * a
local b = fx.c[3] * a
lg.setColor(r, g, b)
else
lg.setColor(fx.c)
end
lg.setBlendMode("replace", "premultiplied")
lg.draw(canvas)
end

function love.keypressed(k)
if k == "space" then frozen = not frozen end
if k == "left" then idx = idx - 2 newBob() end
if k == "right" then newBob() end
return k == "escape" and love.event.quit()
end

You can use space to pause/resume, left/right to change effect, esc to exit.
Attachments
Melonbobs.jpg (25.34 KiB) Viewed 6325 times

veethree
Inner party member
Posts: 820
Joined: Sat Dec 10, 2011 7:18 pm

### Re: Code Doodles!

I've been making music for a long time, And every digital audio workstation i've used has a nifty "tap bpm" function. I was curious how that sort of thing works so i made one in löve. The whole code is in a single file, But i wanted to get all fancy with a custom font and a sound effect.

The weird aspect ratio is due to the fact i made this with my phone in mind.
bpmTapper.love