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!

Post by PGUp » Sun Jul 01, 2018 5:52 am

frac.png
frac.png (26.3 KiB) Viewed 13823 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

function love.load()
math.randomseed(os.time())
lg = love.graphics
lw = love.window
lw.setMode(500, 500)
poly = {}
for i=1, 3, 1 do
local p = {}
p.x = 250 - math.sin(math.rad(60) + i*math.rad(360)/3)*200
p.y = 250 + math.cos(math.rad(60) + i*math.rad(360)/3)*200
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
-

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

Re: Code Doodles!

Post by rmcode » Tue Oct 02, 2018 8:48 pm


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

Re: Code Doodles!

Post by veethree » Thu Nov 08, 2018 5:53 pm

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
1541699302.png (432.87 KiB) Viewed 13044 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)

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

			if add then
				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
		love.load()
	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

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

Re: Code Doodles!

Post by pgimeno » Wed Dec 05, 2018 3:33 pm

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

function love.load()
  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:
Image

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:
Image

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:
Image

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:

Image

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
(808 Bytes) Downloaded 339 times

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

Re: Code Doodles!

Post by veethree » Thu Jan 24, 2019 12:41 am

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
(5.57 KiB) Downloaded 329 times
ga1.png
ga1.png (53.29 KiB) Viewed 11416 times
Attachments
ga2.png
ga2.png (52.65 KiB) Viewed 11416 times

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

Re: Code Doodles!

Post by veethree » Mon Jun 10, 2019 9:53 pm

GOO SIMULATOR 2020

You need moonshine for it to work cause i'm bad at shaders.
1560203306.png
1560203306.png (10.56 KiB) Viewed 9433 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

	useShader = true

	threshold = love.graphics.newShader([[
			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("'1' to toggle shader")
	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()
	if useShader then
		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)
	if useShader then love.graphics.setShader(threshold) end
	love.graphics.draw(snow)
	love.graphics.setShader()

	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
		love.load()
	elseif key == "1" then
		useShader = not useShader
	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
1560203302.png (12.35 KiB) Viewed 9433 times

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

Re: Code Doodles!

Post by pgimeno » Sun May 24, 2020 12:35 am

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 fadetime = 0.8
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
local triShader = lg.newShader[[
  vec4 effect(vec4 col, Image tex, vec2 texpos, vec2 scrpos)
  {
    vec4 c = Texel(tex, texpos);
    return (1. - abs(2.*c - 1.)) * col;
  }
]]

-- "Shadebobs" shader: Like "add" blending mode but with wraparound
local bobShader = lg.newShader[[
  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

function love.load()
  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)
  pcall(bobShader.send, bobShader, 'canvas', canvas)
  lg.setShader(bobShader)
  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.setShader()
  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
    -- Fade out for the last fadetime seconds after t
    local a = max(abs(lissatime - (fx.t + fadetime)) / fadetime, 0)
    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.setShader(triShader)
  lg.draw(canvas)
  lg.setShader()
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
Melonbobs.jpg (25.34 KiB) Viewed 6331 times

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

Re: Code Doodles!

Post by veethree » Mon Jun 01, 2020 11:38 pm

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
(32.67 KiB) Downloaded 142 times
Attachments
bpmTapper.love
(32.67 KiB) Downloaded 141 times
tappergif.gif
tappergif.gif (4.19 MiB) Viewed 6018 times

Post Reply

Who is online

Users browsing this forum: No registered users and 4 guests