Cheap-Ass Metaballs

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

Cheap-Ass Metaballs

Post by timmeh42 » Tue May 01, 2012 4:06 pm

Something I did a while ago in Gamemaker and recently decided to port to Löve (only actually ported it this afternoon) was a small demo of metaballs. Now, I have absolutely no idea how metaballs are meant to be made, but I hear that it involves maths. And I hate maths. Therefore, when I was working on it in GM, I found that a similar effect could be achieved with gradiented images being additively drawn onto a surface, which is then put through a threshold function reducing it to pure black-and-white. The threshold function in GM I used was a really hacked-together thing using recursive rendering in certain blend modes, but when I first thought of doing it in Löve I realised that PixelEffects would be perfect for the job.
SO today I looked through some simple PixelEffect tutorials, and made a very simple BnW shader (it rounds the rgb values to the nearest whole number).
Using this I then went and coded up the rest of the stuff needed for the demo.
I therefore present to you
the Cheap-Ass Metaball System (no, I'm not going to work a Löve-pun into this. EDIT: "Cheap, Ugly Metaballs"?)
so-called because it is a cheap-ass, hackish method of faking metaballs.

Here is a shot showing it giving a cool 985 FPS.
Image

Note that the edges are not anti-aliased at all; this could be remedied by using a more advanced shader. Someone else can do that part.

The way I achieve this is I draw a 64x64 image of a radial sinusoidal gradient from black to white at the points where the metaballs are, using additive blending. This is then passed through the afore-mentioned shader. The gradient must be sinusoidal to achieve the proper effect of the metaballs interacting; a straight-line gradient will give flat edges where they interact.

If you look at the code, be aware that the for-loop is made for a table of only 10 metaballs. This was out of laziness, so just expand it or make it detect the size of the table if you want to use it. Auto-detects table size now, it just assumes the xvalue table is the same length as the yvalue table.

UPDATE:
+ Auto-detects the size of the tables, not hard-coded anymore.
+ The shader now uses 'floor' instead of 'round', which isn't supported by some computers.
Attachments
CAMetaballsB.love
Autodetects table size, now uses floor instead of round in the shader
(2.55 KiB) Downloaded 297 times
CAMetaballs.love
Old version, rather use the B version
(2.54 KiB) Downloaded 146 times
Last edited by timmeh42 on Tue May 01, 2012 6:21 pm, edited 1 time in total.

User avatar
slime
Solid Snayke
Posts: 2923
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Cheap-Ass Metaballs

Post by slime » Tue May 01, 2012 4:35 pm

It looks like your shader code uses a function which isn't officially supported in GLSL 1.20 (which LÖVE uses). http://www.opengl.org/sdk/docs/manglsl/xhtml/round.xml
It will possibly work on some of the more lenient graphics drivers, but it doesn't seem to for me in OSX with an ATI video card and an intel one (both of which do support later GLSL versions).

Adding this to the top of the shader code made it work for me, but I'm not sure what will happen on systems where the driver allows access to the built-in round, so you might want to rename it.

Code: Select all

float round(float x)
{
	return floor(x + 0.5);
}

User avatar
timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

Re: Cheap-Ass Metaballs

Post by timmeh42 » Tue May 01, 2012 5:16 pm

Ah, I didn't realise Löve used such an old version. Too much trouble to use newer version?
Works on my pc, which uses ATI video card (HD4670) but pretty up-to-date drivers (updated about a month ago). Thanks for that bit of code tho, will add it when I get time.

User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Cheap-Ass Metaballs

Post by bartbes » Tue May 01, 2012 5:25 pm

timmeh42 wrote:Ah, I didn't realise Löve used such an old version. Too much trouble to use newer version?
It's a matter of compatibility, by aiming low, you include as many people as possible.

User avatar
timmeh42
Citizen
Posts: 90
Joined: Wed Mar 07, 2012 7:32 pm
Location: Cape Town, South Africa

Re: Cheap-Ass Metaballs

Post by timmeh42 » Tue May 01, 2012 6:22 pm

Right, updated.

UPDATE:
+ Auto-detects the size of the tables, not hard-coded anymore.
+ The shader now uses 'floor' instead of 'round', which isn't supported by some computers.

Also added some line spacing and changed the names of some variables.

User avatar
Tesselode
Party member
Posts: 555
Joined: Fri Jul 23, 2010 7:55 pm

Re: Cheap-Ass Metaballs

Post by Tesselode » Tue May 01, 2012 8:24 pm

Why are people so obsessed with metaballs?

User avatar
ishkabible
Party member
Posts: 241
Joined: Sat Oct 23, 2010 7:34 pm
Location: Kansas USA

Re: Cheap-Ass Metaballs

Post by ishkabible » Tue May 01, 2012 10:49 pm

I want to use this for a fluid simulator; get a bunch of physics balls and display them using meatballs that you created and presto; instance fluid simulator ;)

User avatar
Tesselode
Party member
Posts: 555
Joined: Fri Jul 23, 2010 7:55 pm

Re: Cheap-Ass Metaballs

Post by Tesselode » Tue May 01, 2012 10:59 pm

Haha, "meatballs."

User avatar
slime
Solid Snayke
Posts: 2923
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Cheap-Ass Metaballs

Post by slime » Tue May 01, 2012 11:05 pm

Yeah, portal 2's gels use 3d metaballs for visualization I think.

User avatar
ishkabible
Party member
Posts: 241
Joined: Sat Oct 23, 2010 7:34 pm
Location: Kansas USA

Re: Cheap-Ass Metaballs

Post by ishkabible » Tue May 01, 2012 11:39 pm

proof of concept: this looks just a lot like portal 2's goo. I'm still tweaking the numbers. maybe adding surface tension(attraction between close balls) will give it a better effect.

Code: Select all

function love.load()
	mb_img = love.graphics.newImage("metaball.png")
	canv = love.graphics.newCanvas(800,600)
	effect = love.graphics.newPixelEffect [[
        vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
			vec4 pixel = Texel(texture, texture_coords);
			pixel.r = floor(pixel.r+0.5);
			pixel.g = floor(pixel.g+0.5);
			pixel.b = floor(pixel.b+0.5);
			return  pixel;
		}
	]]

	love.physics.setMeter(64) --the height of a meter our worlds will be 64px
	world = love.physics.newWorld(0, 9.81*64, true) --create a world for the bodies to exist in with horizontal gravity of 0 and vertical gravity of 9.81

	--let's create the ground
	ground = {}
	ground.body = love.physics.newBody(world, 800/2, 600-50/2) --remember, the shape (the rectangle we create next) anchors to the body from its center, so we have to move it to (650/2, 650-50/2)
	ground.shape = love.physics.newRectangleShape(800, 50) --make a rectangle with a width of 650 and a height of 50
	ground.fixture = love.physics.newFixture(ground.body, ground.shape); --attach shape to body

	--let's create a ball
	balls = {}
	for i=1, 300, 1 do
		local x = math.random(-100, 100)
		local y = math.random(-100, 100)
		balls[i] = {}
		balls[i].body = love.physics.newBody(world, 800/2 + x, 600/2 + y, "dynamic") --place the body in the center of the world and make it dynamic, so it can move around
		balls[i].body:setMass(75) --give it a mass of 15
		balls[i].shape = love.physics.newCircleShape(2.75) --the ball's shape has a radius of 10
		balls[i].fixture = love.physics.newFixture(balls[i].body, balls[i].shape, 10) --attach shape to body and give it a friction of 1
		balls[i].fixture:setRestitution(.9) --let the ball bounce
	end
end


function love.update(dt)
	world:update(dt) --this puts the world into motion
end

function love.draw()
	canv:clear(0,0,0,0)
	love.graphics.setBlendMode("additive")
	love.graphics.setCanvas(canv)
	
	for i=1, 100, 1 do
		love.graphics.draw(mb_img, balls[i].body:getX(), balls[i].body:getY())
	end

	love.graphics.setBlendMode("alpha")
	love.graphics.setCanvas()

	love.graphics.setPixelEffect(effect)
	love.graphics.draw(canv,0,0,0,1,1,0,0,0,0)
	love.graphics.setPixelEffect()

	love.graphics.setColor(255,255,255,255)
	love.graphics.print(love.timer.getFPS(),32,32,0,1,1,0,0,0,0)
end
if this was really going to be done right; I would use a faster fluid dynamics algorithm that had a 2D array of density for a given area of the screen; the more pressure the more intense the color(alpha value) and the larger the circle. that would give a REALLY nice effect.

Post Reply

Who is online

Users browsing this forum: No registered users and 54 guests