#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

#### In this Discussion

edited February 2013 in Shaders Posts: 437

I am trying to get this working with Codea:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua

I have recorded a video but the image is flipped, dont know why exactly xD

Here is the Main.lua for Codea

``````balls    = {}
v        = {}
x        = 0
y        = 0
paused   = false
time     = 0
strobo   = false
m        = nil
--[[
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
parameter.number("schwelle",0.002, 2.0, 0.002)
parameter.boolean("drawMesh",false)
local i = 0
for i=1,40 do
balls[i]    = {math.random(0,width), math.random(0,height)}
local v_ges = math.random(200,400)
local v_x   = math.random(0,v_ges)
local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
v[i]        = {v_x, v_y}
end

m = mesh()
m.texture = "Cargo Bot:Codea Icon"
rIdx = m:addRect(0, 0, 0, 0)
m:setRectColor(i, 127,0,0)
m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end

function draw()
background(0)
strokeWidth(2)
fill(17, 120, 223, 255)
stroke(17, 120, 223, 255)
local dt = DeltaTime
time = time + dt
if paused then
return
end
for i=1,#balls do
for j=1,#balls do
if i ~= j then
local left, right
local bottom, top
if balls[i] < balls[j] then
left = i
right = j
else
left = j
right = i
end
if balls[i] < balls[j] then
bottom = i
top = j
else
bottom = j
top = i
end

local x = balls[right]-balls[left]
local y = balls[right]-balls[left]
local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
local f = 200000/math.pow(r,2) * dt
local fx = x/r*f
local fy = y/r*f
balls[left] = balls[left] - fx
balls[right] = balls[right] + fx
balls[left] = balls[left] - fy
balls[right] = balls[right] + fy
end
end
end

for i=1,#balls do
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= width -10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= height-10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
if not drawMesh then
end
end

if drawMesh then
--local cw,ch = spriteSize(m.texture)
--m:setRect(rIdx, WIDTH/2, HEIGHT/2, cw, ch) -- uncomment if texture size changes

-- Configure out custom uniforms for the shader
if strobo then
end

-- Draw the mesh
m:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
paused = not paused
print("paused:",paused)
end
end
``````

This is the vertex program for the metaballs shader:

``````//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform float width;
uniform float height;
uniform float schwelle;
uniform vec2 balls;
uniform float time;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 bColor;
float metaball(vec2 center, vec2 point) {
vec2 v = point-center;
return 1.0/dot(v, v);
}

vec4 effect(
vec4 color, vec2 tex_c,vec2 coord
) {
float val = 0.0;
for (int i =0; i<40; i++) {
val = val + metaball(balls[i],coord);
}

vec4 bar;

if (val < schwelle) {
val = val / schwelle;
bar = vec4(
0.8*(1.0-coord.x/width),
0.8*(1.0-coord.y/height),
0.3*val,
1.0
);
}
else {
bar = vec4(
0.8*coord.x/width,
0.8*coord.y/height,
0.0,1.0
);
}
float foo = mod(time,1.0);
if (foo < 0.1) {
return bar + vec4(vec3(foo*3.14*50.0), 1.0);
}
return bar;
}
void main()
{
//Pass the mesh color to the fragment shader
vColor    = color;
vTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
//Pass the balls effect color to the fragment
bColor    = effect(color, texCoord, vTexCoord);
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;

}

``````

and here it is the fragment program:

``````//
//

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
//balls effect
varying highp vec4 bColor;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );

//Set the output color to the texture color
gl_FragColor = bColor+col;

}

``````

Hope you smart people know How to fix this ;-)

Tagged:

• Posts: 455

Not quite the shader you are referring too, but something in that direction. Performance is poor all code is nicked off the web. Coder as string, so paste it all into main and it should run.

``````--[[
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
numballs = 20
balls = {}
v = {}
x = 0
y = 0
paused = false

parameter.boolean("drawMesh",false)
local i = 0
for i=1,numballs do
local v_ges = math.random(200,400)
local v_x   = math.random(0,v_ges)
local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
v[i]        = {v_x, v_y}
end

m = mesh()
m.texture = "Cargo Bot:Codea Icon"
rIdx = m:addRect(0, 0, 0, 0)
m:setRectColor(i, 127,0,0)
m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end

function draw()
background(0)
output.clear()
print(1/DeltaTime)
strokeWidth(2)
fill(17, 120, 223, 255)
stroke(17, 120, 223, 255)
local dt = DeltaTime/5
if paused then
return
end
for i=1,numballs do
for j=1,numballs do
if i ~= j then
local left, right
local bottom, top
if balls[i] < balls[j] then
left = i
right = j
else
left = j
right = i
end
if balls[i] < balls[j] then
bottom = i
top = j
else
bottom = j
top = i
end

local x = balls[right]-balls[left]
local y = balls[right]-balls[left]
local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
local f = 200000/math.pow(r,2) * dt
local fx = x/r*f
local fy = y/r*f
balls[left] = balls[left] - fx
balls[right] = balls[right] + fx
balls[left] = balls[left] - fy
balls[right] = balls[right] + fy
end
end
end

for i=1,numballs do
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= width -10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= height-10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
if not drawMesh then
ellipse(balls[i],balls[i],balls[i])
end
end

if drawMesh then

-- Draw the mesh
m:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
paused = not paused
print("paused:",paused)
end
end

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

varying vec2 vtexCoord;
void main()
{
vtexCoord = texCoord;
gl_Position = modelViewProjection * position;

}

]],

//
//

//This represents the current texture on the mesh
uniform highp vec3 balls;
precision mediump float;

uniform float u_width;
uniform int numballs;

varying vec2 vtexCoord;

float energyField(vec2 p, float iso)
{
float en = (balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)))
+(balls.z / max(0.0001, length(balls.xy - p)));
return (en - iso);
}

void main()
{
float power = energyField(vtexCoord.xy * u_width, 3.0);
// got the power, now add our 'flourish'...
float hwidth = u_width/2.0;
// rescale from 0 -> width to -1 -> 1
float left = 1.0 - (vtexCoord.x/hwidth);
left = (left * left) * (left * left);
gl_FragColor = vec4(power-(left*2.0), power-left, power-left, left+0.5);
}

]]}
``````
• Posts: 2,161

Not really sure what the effect should look like, so this is just a pointer. Main things: the shader looks like a fragment shader, not a vertex one. And you passed the balls' centres as an array of arrays of floats, not an array of vec2s.

``````balls    = {}
v        = {}
x        = 0
y        = 0
paused   = false
time     = 0
strobo   = false
m        = nil
--[[
original source code , love2d version:
https://github.com/vincent23/love2d-experiments/blob/master/metaball/main.lua
]]--
displayMode(STANDARD)
function setup()
parameter.number("schwelle",0.002, 2.0, 0.002)
parameter.boolean("drawMesh",false)
local i = 0
for i=1,40 do
balls[i]    = vec2(math.random(0,width),
math.random(0,height))
local v_ges = math.random(200,400)
local v_x   = math.random(0,v_ges)
local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
v[i]        = {v_x, v_y}
end

m = mesh()
m.texture = "Cargo Bot:Codea Icon"
rIdx = m:addRect(0, 0, 0, 0)
m:setRectColor(i, 127,0,0)
m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end

function draw()
background(0)
strokeWidth(2)
fill(17, 120, 223, 255)
stroke(17, 120, 223, 255)
local dt = DeltaTime
time = time + dt
if paused then
return
end

for i=1,#balls do
for j=1,#balls do
if i ~= j then
local left, right
local bottom, top
if balls[i].x < balls[j].x then
left = i
right = j
else
left = j
right = i
end
if balls[i].y < balls[j].y then
bottom = i
top = j
else
bottom = j
top = i
end

local x = balls[right].x-balls[left].x
local y = balls[right].y-balls[left].y
local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
local f = 200000/math.pow(r,2) * dt
local fx = x/r*f
local fy = y/r*f
balls[left].x = balls[left].x - fx
balls[right].x = balls[right].x + fx
balls[left].y = balls[left].y - fy
balls[right].y = balls[right].y + fy
end
end
end

for i=1,#balls do
balls[i].x = balls[i].x + v[i] * dt
if (balls[i].x < 10 and v[i] < 0) or (balls[i].x >= width -10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
balls[i].y = balls[i].y + v[i] * dt
if (balls[i].y < 10 and v[i] < 0) or (balls[i].y >= height-10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
if not drawMesh then
end
end

if drawMesh then
local cw,ch = spriteSize(m.texture)
--m:setRect(rIdx, WIDTH/2, HEIGHT/2, cw, ch) -- uncomment if texture size changes

-- Configure out custom uniforms for the shader
if strobo then
end

-- Draw the mesh
m:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
paused = not paused
print("paused:",paused)
end
end
``````

``````//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vColor    = color;
vTexCoord = texCoord;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;

}
``````

``````//
//
precision highp float;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform float width;
uniform float height;
uniform float schwelle;
uniform vec2 balls;
uniform float time;
highp vec2 size = vec2(width,height);
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

float metaball(vec2 center, vec2 point) {
vec2 v = size*point-center;
float l = dot(v,v);
if (l < .05) return 2.;
return .1/l;
}

lowp vec4 effect(
highp vec2 coord
) {
float val = 0.0;
for (int i =0; i<40; i++) {
val = val + metaball(balls[i],coord);
}

vec4 bar;

if (val < schwelle) {
val = val / schwelle;
bar = vec4(
0.8*(1.0-coord.x),
0.8*(1.0-coord.y),
0.3*val,
1.0
);
}
else {
bar = vec4(
0.8*coord.x,
0.8*coord.y,
0.0,1.0
);
}
//float foo = mod(time,1.0);
//if (foo < 0.1) {
//return bar + vec4(vec3(foo*3.14*50.0), 1.0);
//}
return bar;
}

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );
//Pass the balls effect color to the fragment
lowp vec4 bColor    = effect(vTexCoord);
//Set the output color to the texture color
gl_FragColor = bColor*col;

}
``````
• Posts: 437

.@spacemonkey you did it!!
Here is a video

.@andrew_stacey ok, I understand now thanks

• Posts: 437

Here it is another approximation using this shader:
http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

Lua code:

``````displayMode(FULLSCREEN)
function setup()
numballs = 12
balls = {}
v = {}
x = 0
y = 0
width  = WIDTH
height = HEIGHT
paused = false
drawMesh = true

local i = 0
for i=1,numballs do
balls[i]    = vec2(math.random(0,width), math.random(0,height))
local v_ges = math.random(333,666)
local v_x   = math.random(0,v_ges)
local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
v[i]        = {v_x, v_y}
end

m = mesh()
--m.texture = "Cargo Bot:Codea Icon"
rIdx = m:addRect(0, 0, 0, 0)
m:setRectColor(i, 127,0,0)
m:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end

function draw()
background(0)
output.clear()
--print(1/DeltaTime)
strokeWidth(2)
fill(17, 120, 223, 255)
stroke(17, 120, 223, 255)
local dt = DeltaTime/5

if paused then
return
end
for i=1,numballs do
for j=1,numballs do
if i ~= j then
local left, right
local bottom, top
if balls[i] < balls[j] then
left = i
right = j
else
left = j
right = i
end
if balls[i] < balls[j] then
bottom = i
top = j
else
bottom = j
top = i
end

local x = balls[right]-balls[left]
local y = balls[right]-balls[left]
local r = math.sqrt(math.pow(x,2) + math.pow(y,2))
local f = 200000/math.pow(r,2) * dt
local fx = x/r*f
local fy = y/r*f
balls[left] = balls[left] - fx
balls[right] = balls[right] + fx
balls[left] = balls[left] - fy
balls[right] = balls[right] + fy
end
end
end

for i=1,numballs do
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= width -10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= height-10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
if not drawMesh then
end
end

if drawMesh then

-- Draw the mesh
m:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
--paused = not paused
print("paused:",paused)
elseif touch.state == MOVING then
balls = touch.x
balls = touch.y
end
end

``````

``````//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

varying vec2 vtexCoord;
void main()
{
vtexCoord = texCoord;
gl_Position = modelViewProjection * position;

}

``````

``````//
//

//This represents the current texture on the mesh
uniform int numballs;
uniform highp vec2 balls;
precision mediump float;
varying vec2 vtexCoord;

void main()
{
float sum = 0.0;
float size = 66.0;
float r = 23.0;
float g = 0.66;
for (int i = 0; i <= numballs; ++i) {

float dist = length(gl_FragCoord.xy - balls[i]);

sum += size / pow(dist, g);
}

vec3 color = vec3(0,0,0);
if (sum>r) color = vec3(r/sum,r/sum,1);

gl_FragColor = vec4(color, 1);
}

``````
• Posts: 437

OK, now we have physics, we can say this is the first fluid simulation in Codea ``````-- 2DWater
supportedOrientations(LANDSCAPE_RIGHT)
-- Use this function to perform your initial setup
function setup()
numDrops = 12
drops    = {}
wallWidth= 6
-- create ground
ground = physics.body(POLYGON, vec2(0,wallWidth),
vec2(0,0), vec2(WIDTH,0), vec2(WIDTH,wallWidth))
ground.type = STATIC
-- create roof
roof = physics.body(POLYGON,
vec2(0,HEIGHT),
vec2(0,HEIGHT-wallWidth),
vec2(WIDTH,HEIGHT-wallWidth), vec2(WIDTH,HEIGHT)
)
roof.type = STATIC
-- create walls
lwall = physics.body(POLYGON, vec2(0,HEIGHT), vec2(0,0), vec2(-2,0), vec2(-2,HEIGHT))
lwall.type = STATIC
rwall = physics.body(POLYGON, vec2(WIDTH+1,HEIGHT),
vec2(WIDTH+1,0),
vec2(WIDTH,0), vec2(WIDTH,HEIGHT))
rwall.type = STATIC
effect   = mesh()
for i = 1, numDrops do
drops[i].x = math.random(d,WIDTH - d)
drops[i].y = math.random(d,HEIGHT - d)
drops[i].type = DYNAMIC
drops[i].interpolate = true
drops[i].restitution = 0.25
drops[i].sleepingAllowed = false
end
effect:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
defaultGravity = physics.gravity()
parameter.boolean("UseGravity",false)
end

function mpos()
r = {}
for i= 1, numDrops do
r[i] = drops[i].position
end
return r
end

function drawWall(w)
local points = w.points
for j=1, #points do
a = points[j]
b = points[(j % #points)+1]
line(a.x,a.y,b.x,b.y)
end
end

-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)

-- This sets the line thickness
strokeWidth(5)
stroke(255)
-- chose drawing system
effect:setRect(rIdx, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
effect:draw()
else
for i=1, numDrops do
end
end
if UseGravity then
physics.gravity (Gravity*10)
else
physics.gravity ( defaultGravity)
end
-- draw ground
drawWall(ground)
end
``````

The shader is the same of my last post here.

This is the result in the video:

1) there are only balls with default Gravity
moving around, water simulation ready • Posts: 3,297

That's fantastic!

• Posts: 437

Thanks @Jmv38 ,has anyone tried this webgl shader?
wouldnt be amazing to have this working with our beloved Codea? )

• Posts: 455

That's a fairly hectic example. It contains multiple shaders, the biggest barrier is it uses a couple of capabilities which we don't have. Specifically it uses textures in vertex shaders which the iPad doesn't support. It looks like it uses some cube maps which I'm not sure if codea supports. Also it uses some extensions such as OES_Texture_Float and I'm not sure whether ipad has this or not and if so how you use it via codea.

But other than that, it would be amazing ;-)

• Posts: 437

I understand, maybe we just need to understand how this kind of shaders ( vertex, fragment and pixel shaders...because we cant use geometry shaders with a good framerate ) works to replicate the behaviour of the surface in the water of this video (3d):

Shaders explained here in this video at 1:35

• edited May 2013 Posts: 5,396

You can create a fairly simple moving water effect by using noise and overlaying a slightly transparent rectangle. The video below shows the result using the noise demo project with a single line of code to overlay a rect, showing the effect of using different colours and alpha values to create water, cloud, dust, etc. it's amazing how much it improves the basic noise effect.

NB the original looks better than the video

• edited May 2013 Posts: 437

@Ignatz I think that is faster but with low res , do you think that we can achieve to traslate this
http://www.bonzaisoftware.com/water_tut.html#glsl
- GLSL program:

or this one!:

• Posts: 5,396

I think Codea is going to struggle with detailed 3D surface rendering, but I am not an expert in this, so don't take my word for it.

• Posts: 437

This is a new version based on processing, this time I'm using the virtual space of an image to read/write values of the metaball...it is still too slow for gaming...
Opinions?

``````function setup()
metaball = Metaball()
end

function draw()
background(0)
metaball:draw()
end

--[[
* @Original code Info
*
* from Proce55ing
* Metaball Demo Effect
* by luis2048.
*
]]--
Metaball = class()
TOTAL_BALL_COUNT = 6
STAGE_W  = WIDTH
STAGE_H  = HEIGHT
BT_WIDTH = 80
BT_HEIGHT= 80

function Metaball:init()
self.bitmap = image(BT_WIDTH,BT_HEIGHT)
self.bitmapData = self.bitmap.data
self.balls_ary  = {}
self.count = 0
self.mouseX = 0
self.mouseY = 0
for i = 0 , TOTAL_BALL_COUNT do
self.balls_ary[i] = Ball(
vec2(random1(), random1()),
vec2(math.random(1,BT_WIDTH) , math.random(1,BT_HEIGHT)),
self
)

end

self.width  = STAGE_W
self.height = STAGE_H
count = Math.random()*10
self:bomb()
end

function Metaball:touched(touch)
if touch.state = ENDED then
self.count = self.count + 1
self:bomb()
end
end

function Metaball:bomb()
for i = 0 , TOTAL_BALL_COUNT  do
self.balls_ary[i]:bomb()
end
end

function Metaball:draw(evt:Event):void {
for i = 0 , TOTAL_BALL_COUNT do
self.balls_ary[i]:update()
end
self:render()
end

function Metaball:render()
--bitmapData.lock()
for y = 0 , BT_HEIGHT do
for x = 0 , BT_WIDTH do
local pixelsValue = 0
for i = 0,  TOTAL_BALL_COUNT do
local ball = self.balls_ary[i]
pixelsValue = pixelsValue + ball.currentRadius / (1 + ball:getPixelValue(x, y))
end

self.bitmapData:set(x, y, self:convertRGBColor(pixelsValue))
end
end
--bitmapData.unlock()
sprite(self.bitmap, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end

function Metaball:convertRGBColor(pixelsValue)
local c   = math.fmod(count, 6)
if c == 0 then
return self:getRGB(0, pixelsValue/2 , pixelsValue)
elseif c == 1 then
return self:getRGB( pixelsValue, pixelsValue / 2, 0)
elseif c == 2 then
return self:getRGB( pixelsValue, pixelsValue/3, pixelsValue/2)
elseif c == 3 then
return self:getRGB( pixelsValue/2, pixelsValue*0.8, pixelsValue/5)
elseif c == 4 then
return self:getRGB( pixelsValue*0.8, pixelsValue/4, pixelsValue/7)
elseif c == 5 then
return self:getRGB(pixelsValue/6, pixelsValue/3 , pixelsValue*0.8)
end
end

function Metaball:shuffle(ary)
local i = #ary
while (i>0) do
local j = math.floor(math.random()*(i+1))
local t = ary[i]
ary[i] = ary[j]
ary[j] = t
i = i - 1
end
return ary
end

function Metaball:getRGB(red , green , blue )
return (math.min(red, 255)*2*16 or math.min(green, 255)*2*8 or math.min(blue, 255))
end

--[[function random(min , max)
if (max == min) {
return max
}else if (max < min) {
var _temp: Number = max
max = min
min = _temp
}
return Math.random() * (max - min) + min
}
]]--

function Metaball:getPoint()
return vec2(
BT_WIDTH * ( self.mouseX / STAGE_W),
BT_HEIGHT* ( self.mouseY / STAGE_H)
)
end

Ball = class ()
function Ball:init(vel , pos, metaballfather)
self.pixelX_ary = {}
self.pixelY_ary = {}
self.velocity   = vel
self.position   = pos
self.friction   = vec2(0,0)
self.metaball   = metaballfather
self:reset()
end

function Ball:update()
self.position = self.position + self.velocity
self.velocity = self.velocity - self.friction
self:checkBorderline()
self:setPixels()
end

function Ball:checkBorderline()
if self.position.y > (BT_HEIGHT + 10) then
self:reset()
self.velocity.y = 1
self.position.y = -10
elseif (self.position.y < -10) then
self:reset()
self.velocity.y = -1
self.position.y = BT_HEIGHT + 10
end

if (self.position.x > (BT_WIDTH+ 10)) then
self:reset()
self.position.x = -10
self.velocity.x = 1
else if (position.x < -10) then
self:reset()
self.position.x = BT_WIDTH + 10
self.velocity.x = -1
end
end

function Ball:reset()
self.friction= vec2(random1() * math.random() / 50 , random1() * math.random() / 50)
end

function Ball:bomb()
self:reset()
self.position = self.metaball.getPoint()
end

function Ball:setPixels()
self.pixelX_ary = {}
self.pixelY_ary = {}
for (y = 0 , BT_HEIGHT ) do
self.pixelY_ary[y] = ((self.position.y - y)*(self.position.y - y))
end
for (x= 0 , BT_WIDTH ) do
self.pixelX_ary[x] = ((position.x - x)*(position.x - x))
end
end

function Ball:getPixelValue(x , y)
return self.pixelX_ary[x] + self.pixelY_ary[y]
end

function random1(pct)
if pct == nil then pct =  0.5 end
if math.random() < pct then
return 1
else
return -1
end
end
``````
• Posts: 455

I had a idea on this, it does the metaballs by first creating a texture representing the fall off for a point, then rendering all points addatively to an off screen image, and then finally using that image as a texture to render back to screen based on a threshold.

It's a bit ugly (2 color) but the methodology could definitely be extended and it's very fast. It's also a little fuzzy round the edges...

``````displayMode(STANDARD)
function setup()
numballs = 30
balls = {}
v = {}
x = 0
y = 0
paused = false

parameter.boolean("thresholded",true)
parameter.number("threshold",0,1.0,0.7)
local i = 0
for i=1,numballs do
local v_ges = math.random(200,400)
local v_x   = math.random(0,v_ges)
local v_y   = math.sqrt(math.pow(v_ges,2) - math.pow(v_x,2))
v[i]        = {v_x, v_y}
end

--create a texture for the falloff curve
img = image(500,500)
setContext(img)
for i=250,1,-1 do
--fill(255/100*(100-i),255)
fill(255*1/((i/20)^2),255)
ellipse(250,250,i*2)
end

ballSize = 500

ballMesh = mesh()
ballMesh:setRectTex(1, 0,0,1,1)
ballMesh.texture = img
--ballMesh:setRect(rIdx, 0, 0, ballSize, ballSize)
firstPass = image(WIDTH,HEIGHT)
secondPass = mesh()
secondPass:setRectTex(1,0,0,1,1)
secondPass.texture = firstPass
end

function draw()
output.clear()
print(1/DeltaTime)
local dt = DeltaTime/5
if paused then
return
end
if thresholded then
setContext(firstPass)
end
background(0)
for i=1,numballs do

balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= width -10 and v[i] > 0) then
v[i] = v[i] * (-1)
end
balls[i] = balls[i] + v[i] * dt
if (balls[i] < 10 and v[i] < 0) or (balls[i] >= height-10 and v[i] > 0) then
v[i] = v[i] * (-1)
end

translate(balls[i],balls[i])
ballMesh:draw()
resetMatrix()
end
if thresholded then
setContext()
background(0)
secondPass:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
paused = not paused
print("paused:",paused)
end
end

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = gl_LastFragData;

col.xyz += texture2D( texture, vTexCoord ).xyz;
col.a = 1.0;
//Set the output color to the texture color
gl_FragColor = col;
}
]] }

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp float threshold;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );

if (col.r < threshold) {
col = vec4(0.0,0.0,0.0,0.0);
}
else {
col = vec4(1.0,1.0,1.0,1.0);
}

//Set the output color to the texture color
gl_FragColor = col;
}
]] }
``````
• Posts: 2,820

I wish I had seen this before I made my own version. I'll post my version once I finish optimizing it.

• edited July 2013 Posts: 5,396

@spacemonkey - what does this do?

• Posts: 455

@Ignatz it enables an extension beyond the base OpenGL ES capabilities. Extensions allow hardware manufacturers to add interesting things as options on their gear, but of course it reduces the compatability of the shader as it now only works on hardware that supports the extension.

Anyway, this specific one allows you to read data back from the current framebuffer. So in the fragment shader I can additively blend to the current screen, gl_LastFragData; reads the current color of the pixel from the screen (or image if you are using setContext) when you are in the fragment shader.

• Posts: 455

Here's another one, it takes my old bouncing balls, and applies the metaballs approach as above.

``````displayMode(STANDARD)
function setup()
balls = {}
touches = {}
nextball = 1

base = physics.body(EDGE, vec2(100,0), vec2(WIDTH-100,0))

parameter.boolean("thresholded",true)
parameter.number("threshold",0,1.0,0.7)

--create a texture for the falloff curve
img = image(500,500)
setContext(img)
for i=250,1,-1 do
--fill(255/100*(100-i),255)
fill(255*1/((i/20)^2),255)
ellipse(250,250,i*2)
end

ballSize = 25

ballMesh = mesh()
ballMesh:setRectTex(1, 0,0,1,1)
ballMesh.texture = img
--ballMesh:setRect(rIdx, 0, 0, ballSize, ballSize)
firstPass = image(WIDTH,HEIGHT)
secondPass = mesh()
secondPass:setRectTex(1,0,0,1,1)
secondPass.texture = firstPass
end

function touched(touch)
if touch.state == ENDED then
touches[touch.id] = nil
else
--if touches[touch.id] == nil then
touches[touch.id] = touch
--end
end
end

function touchActions()
for k,v in pairs(touches) do
if CurrentTouch.state == ENDED then
--if there are no current touches then we kill all current touches to avoid bugged ball producers
touches[k] = nil
else

--add a new ball at the touch location
size = math.random(1,20)
tspot = physics.body(CIRCLE, size)
tspot.position = vec2(v.x+math.random(-1,1), v.y+math.random(-1,1))
tspot.restitution = 0.95

balls[nextball] = { tspot = tspot, size = size * 2, r = math.random(30,255), g = math.random(30,255), b = math.random(30,255) }

nextball = nextball + 1
end
end
end

function draw()

if thresholded then
setContext(firstPass)
end
background(0)
for k,v in pairs(balls) do
if v.tspot.x < -20 or v.tspot.x > WIDTH + 20 or v.tspot.y < -20 then
balls[k].tspot:destroy()
balls[k] = nil
else
resetMatrix()
translate(v.tspot.x,v.tspot.y)
scale(v.size)

--[[
]]
ballMesh:setColors(color(v.r,v.g,v.b,255))

ballMesh:draw()
--fill(v.r, v.g, v.b, 255)
--ellipse(v.tspot.x, v.tspot.y, v.size)
end
end
if thresholded then
resetMatrix()
setContext()
background(0)
secondPass:draw()
end
touchActions()
end

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = gl_LastFragData;

col.xyz += texture2D( texture, vTexCoord ).xyz;
col.a = 1.0;
//Set the output color to the texture color
gl_FragColor = col;
}
]] }

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;
uniform lowp float threshold;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );

if (col.r < threshold) {
col = vec4(0.0,0.0,0.0,0.0);
}
else {
col = vec4(1.0,1.0,1.0,1.0);
}

//Set the output color to the texture color
gl_FragColor = col;
}
]] }
``````
• edited July 2013 Posts: 437

I only see a black screen running in an iPad2 with iOS 5.1 and Codea 1.5.4(2).
Error:

• Posts: 455

Interesting. It's definitely supported hardware wise on iPad2 cos that's what I have, I guess it's iOS 5, I am running 6.

• Posts: 437

Yes @spacemonkey ,that extension was added with iOS6 beta • edited July 2013 Posts: 1,976

``````-- Metaballs-4

-- Use this function to perform your initial setup
function setup()
print("Hello World!")

m = {}

else
end
end)

parameter.boolean("Animated", false)

parameter.boolean("Show_Preview", true)

parameter.integer("Number_of_Balls", 1, 1000, 25, function()
balls = {}
ballData = {}
for i=1,Number_of_Balls do
table.insert(balls, vec2(math.random(0, WIDTH), math.random(0, HEIGHT)))
table.insert(ballData, vec2(math.random(-1, 1), math.random(-1, 1)))
end
end)

parameter.integer("Metaball_Size", 1, 200, 100)

parameter.integer("Metaball_Resolution", 1, 255, 255)

parameter.action("Register Resolution (May lag!)", function()
GENERATE_METABALL()
end)

parameter.integer("FPS_Smoothing", 1, 100, 50)

parameter.action("Restart", function()
balls = {}
setContext(img)
background(0)
setContext()
restart()
end)

function GENERATE_METABALL()

local mr = Metaball_Resolution
local ms = Metaball_Size

blendMode(NORMAL)

ballTex = image(200, 200)
setContext(ballTex)
pushStyle()
noStroke()
for i = 255, 0, -(256 - mr) do
--[[
fill(128)
ellipse(100, 100, 100, 100)
fill(191)
ellipse(100, 100, 75, 75)
fill(255)
ellipse(100, 100, 25, 25)
popStyle()
--]]

fill(255 - i, 255 - i)
local K = 1000
ellipse(100, 100, (i / 255) * ms, (i / 255) * ms)

--print(i)
--print(255 - i, 255 - i)
--print((i / 255) * 100)

--setContext()
end

setContext()

end

GENERATE_METABALL()

balls = {}
ballData = {}
for i=1,Number_of_Balls do
table.insert(balls, vec2(math.random(0, WIDTH), math.random(0, HEIGHT)))
table.insert(ballData, vec2(math.random(-1, 1), math.random(-1, 1)))
end

img = image(WIDTH, HEIGHT)

m = mesh()
r = m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
m:setRectTex(r, 0, 0, 1, 1)
m.texture = img
end

FPSHistory = {}
end

-- This function gets called once every frame
function draw()

output.clear()
table.insert(FPSHistory, 1 / DeltaTime)

while #FPSHistory > FPS_Smoothing do
table.remove(FPSHistory, 1)
end

local result = 0

for k,v in ipairs(FPSHistory) do
result = result + v
end

print(result / #FPSHistory)

-- This sets a dark background color
background(0)

-- This sets the line thickness
strokeWidth(5)

if Animated then
for i = 1, Number_of_Balls do
local b = balls[i]
local bd = ballData[i]

if b.x > WIDTH then
bd.x = -math.abs(bd.x)
end

if b.x < 0 then
bd.x = math.abs(bd.x)
end

if b.y > HEIGHT then
bd.y = -math.abs(bd.y)
end

if b.y < 0 then
bd.y = math.abs(bd.y)
end

b.x = b.x + bd.x
b.y = b.y + bd.y
end
end

setContext(img)
background(0)
for k,v in ipairs(balls) do
sprite(ballTex, v.x, v.y)
end
setContext()
m.texture = img
m:draw()

if Show_Preview then
blendMode(NORMAL)

fill(0)

stroke(127)

strokeWidth(5)

rect(0, 0, 200, 200)

noStroke()

sprite(ballTex, 100, 100)

fill(255)
font("Inconsolata")
fontSize(12)
textMode(CENTER)
text("Metaball Compution Preview:", 100, 150)
end
end

function touched(touch)
if touch.state == BEGAN or touch.state == MOVING then
if balls[Number_of_Balls + 1] == nil then
table.insert(balls, vec2(touch.x, touch.y))
else
balls[Number_of_Balls + 1] = vec2(touch.x, touch.y)
end
end
end

vS = [[
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]]

fS = [[
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;

//Set the output color to the texture color
if (max(col.r, max(col.g, col.b)) > 0.75)
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
else
{
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}
]]
``````

It only gets about 10 FPS... At a thousand Metaballs. :P

Mine uses the simplest math to calculate bridges...none. It just draws a bunch of shaded balls on the screen with the ADDITIVE blend mode. Then it has a shader check if a pixel is bright enough, if it is it renders it white, if it isn't it renders it transparent. Turn off the shader using the parameter to see what I mean. The shader just filters out the gradients.

Sorry for lots of commented-out lines and messy code. I'm too lazy to clean it up. :P

Steady 50 FPS at a hundred Metaballs. • Posts: 437

I really like this piece of code, since there is a generated texture you could add a noise to give more "water" appearance

• Posts: 1,976

Yep. I'm also gonna work on a shader for the screen, that replaces the white with a specific texture.

• edited July 2013 Posts: 455

Here's one where I encode color across the falloff (dark blue middle, light blue ring, then yellow falloff). Somethings not right that I haven't debugged, you might notice some strange internal artifacts, but it pretty much works. It's slower though... Probably would be better to colorise on a final pass perhaps.

Code tweaked because I realised colorising the source falloff texture was unecessary and caused more calculation

``````displayMode(STANDARD)
function setup()
displayMode(FULLSCREEN)
balls = {}
touches = {}
nextball = 1

base = physics.body(EDGE, vec2(100,0), vec2(WIDTH-100,0))

--create a texture for the falloff curve
size = 500
img = image(size,size)
setContext(img)

fill(255*1/((i/15)^2),255)
end

ballSize = 25

ballMesh = mesh()
ballMesh:setRectTex(1, 0,0,1,1)
ballMesh.texture = img
--ballMesh:setRect(rIdx, 0, 0, ballSize, ballSize)
firstPass = image(WIDTH,HEIGHT)
end

function touched(touch)
if touch.state == ENDED then
touches[touch.id] = nil
else
--if touches[touch.id] == nil then
touches[touch.id] = touch
--end
end
end

function touchActions()
for k,v in pairs(touches) do
if CurrentTouch.state == ENDED then
--if there are no current touches then we kill all current touches to avoid bugged ball producers
touches[k] = nil
else

--add a new ball at the touch location
size = math.random(1,20)
tspot = physics.body(CIRCLE, size)
tspot.position = vec2(v.x+math.random(-1,1), v.y+math.random(-1,1))
tspot.restitution = 0.95

balls[nextball] = { tspot = tspot, size = size * 2, r = math.random(30,255), g = math.random(30,255), b = math.random(30,255) }

nextball = nextball + 1
end
end
end

function draw()

background(0)
--sprite(img,WIDTH/2,HEIGHT/2,1000,1000)
for k,v in pairs(balls) do
if v.tspot.x < -20 or v.tspot.x > WIDTH + 20 or v.tspot.y < -20 then
balls[k].tspot:destroy()
balls[k] = nil
else
resetMatrix()
translate(v.tspot.x,v.tspot.y)
scale(v.size)

--[[
]]
ballMesh:setColors(color(v.r,v.g,v.b,255))

ballMesh:draw()
--fill(v.r, v.g, v.b, 255)
--ellipse(v.tspot.x, v.tspot.y, v.size)
end
end
touchActions()
end

//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

float numberize(lowp vec3 col)
{
float value;
if (col.z == 0.0) {
//this is level 1
value = col.x;
}
else if (col.x > 0.5) {
//this is level 2
value = 1.0 + col.z;
}
else {
//this is level 3
value = 2.0 + (1.0-(col.z - 0.5)*2.0);
}
return value;
}

lowp vec4 colorize(float value)
{
lowp vec4 col;
if (value <= 1.0) {
//level 1
col = vec4(value,value,0.0,1.0);
}
else if (value <= 2.0) {
//level 2
col = vec4(1.0 - (value - 1.0)/2.0, 1.0 - (value - 1.0)/2.0, value - 1.0, 1.0);
}
else if (value <= 3.0) {
col = vec4(0.49, 0.49, 1.0 - (value - 2.0)/2.0, 1.0);
}
else {
col = vec4(0.49,0.49,0.49, 1.0);
}
return col;
}

void main()
{
//Sample the texture at the interpolated coordinate
float curVal = numberize(gl_LastFragData.xyz);
//curVal = curVal + numberize(texture2D( texture, vTexCoord ).xyz);
curVal = curVal + texture2D( texture, vTexCoord ).x * 3.0;

lowp vec4 col = colorize(curVal);
//vec4(0.0,0.0,curVal,1.0);
//col.xyz += texture2D( texture, vTexCoord ).xyz;
//col.a = 1.0;
//Set the output color to the texture color
gl_FragColor = col;

}
]] }

``````
• edited July 2013 Posts: 437

.@spacemonkey I have exported the code to a project and then run it in an iPhone4 with iOS6.1.1 and it appears the next warning:

``````Shader compile log:
WARNING: 0:58: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
``````

But it worked!, so weird when the balls are created I cant understand how are you coloring the balls, where are you defining the "power of yellow"? I would like to change it and try with another layer in multiply mode...

Here it is a video:

• Posts: 455

@jauxix I think iPhone doesn't support high precision, change the shader line: precision highp float; to precision mediump float; and it might work.

For the coloring it's the colorize and numberize that do the work. Effectively I treat the strength in a pixel as a number from 0.0 - 3.0. It encodes 0.0 - 1.0 as black through to bright yellow, then 1.0 - 2.0 as the yellow declining to half strength while blue comes to full strength, and finally 2.0 to 3.0 as the blue declining to half strength (ie ending in mid grey).

Colorize builds the right color for this in a fairly clear way. The numberize is doing the reverse, but it looks at the various values of the color to decode it. So if the blue is 0.0 then I know it must be in the 0.0 - 1.0 range as the other 2 ranges include blue the strength within that range (decimal part) is then just the red or green value. If the blue has some value, and the red/green is still strong than 0.5 then it must be in 1.0 - 2.0 as that finishes with the red/green just below 0.5 the decimal part is then just blue. Finally if red/green is below 0.5 it must be in the 3rd range 2.0 - 3.0 and the strength will be where the blue is in it's change from full strength to half strength.

• Posts: 437

I dont understand...so, if we want to turn it like the @Zoyt version here:
http://twolivesleft.com/Codea/Talk/discussion/3200/realistic-2d-water-effect
we need to change the values to encode blue in stead of yellow, in the numberize function or the colorize? >:D<

• Posts: 455

Hmmm... I described my algorithm, I haven't looked at the other code (and I don't have my ipad here today), but you need to conceptually come up with a colour pattern you like and then think how you can translate from that color pattern to a strength and back. Then those algorithms for translation get put into numberize (color in, strength out) and colorize (strength in, color out).

If you do an image of the kind of color gradient you want to use I'd happily look at it and see if I can implement it.

• edited July 2013 Posts: 437

Hey, @SkyTheCoder, here it is a mod of your code with some color and box2d physics.
Thanks for the code, im using it in 6Dimensions game!

``````displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
parameter.watch("#balls")
parameter.watch("math.floor(1/DeltaTime)")

ground = physics.body(EDGE, vec2(0,0), vec2(WIDTH,0))
ground.type = STATIC
ramp = physics.body(EDGE,
vec2(WIDTH/2-333, HEIGHT/2-100),
vec2(WIDTH/2-66, HEIGHT/2+100))

ramp2 = physics.body(EDGE,
vec2(WIDTH/2+66, HEIGHT/2+100),
vec2(WIDTH/2+333, HEIGHT/2-100))
m = {}
Number_of_Balls = 333

Metaball_Size = 79
parameter.integer("Metaball_Resolution", 1, 255, 255)
parameter.action("Register Resolution (May lag!)", function()
GENERATE_METABALL()
end)
function GENERATE_METABALL()
local mr = Metaball_Resolution
local ms = Metaball_Size
blendMode(NORMAL)
ballTex = image(200, 200)
setContext(ballTex)
pushStyle()
noStroke()
for i = 255, 0, -(256 - mr) do
fill(255 - i, 255 - i)
ellipse(100, 100, (i / 255) * ms, (i / 255) * ms)
end
setContext()
end

GENERATE_METABALL()
balls = {}
for i=1,Number_of_Balls do
local ball = createDrop( math.random(0, WIDTH), math.random(HEIGHT/2+130, HEIGHT))
table.insert(balls, ball)
end

img = image(WIDTH, HEIGHT)
m = mesh()
r = m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
m:setRectTex(r, 0, 0, 1, 1)
m.texture = img
end

function createDrop(x,y)
local ball = physics.body(CIRCLE, 12)
ball.x = x
ball.y = y
ball.restitution = .1
ball.linearVelocity = vec2(0,0)
ball.friction = 0.1
ball.mass = 10
ball.angularVelocity = 0.0
ball.bullet = false
ball.linearDamping = 0.1
return ball
end

-- This function gets called once every frame
function draw()
-- remove invisible balls... here to avoid flickering
for k,b in ipairs(balls) do
if b.x+Metaball_Size> WIDTH +Metaball_Size *2 or b.x<-Metaball_Size then
table.remove(balls, k)
b:destroy()
end
end
blendMode(NORMAL)
background(0)

sprite("SpaceCute:Background",WIDTH/2,HEIGHT/2, WIDTH,HEIGHT)

rectMode(CENTER)
stroke(255, 0, 0,255)

strokeWidth(3)
line(WIDTH/2-333, HEIGHT/2-100,
WIDTH/2-66, HEIGHT/2+100)
line(WIDTH/2+66, HEIGHT/2+100,
WIDTH/2+333, HEIGHT/2-100)

setContext(img)
background(0) -- clear buffer
--tint(139, 145, 157, 255)
for k,b in ipairs(balls) do
sprite(ballTex, b.x, b.y)
end
--noTint()
setContext()
m.texture = img
blendMode(MULTIPLY)
m:draw()

end

function touched(touch)
if touch.state == BEGAN or touch.state == MOVING then
if balls[Number_of_Balls + 1] == nil then
local ball = createDrop(touch.x,touch.y)
table.insert(balls, ball)
else
balls[Number_of_Balls + 1].x = touch.x
balls[Number_of_Balls + 1].y = touch.y
end
end
end

vS = [[
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;

//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]]

fS = [[
//
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;

//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;

//Set the output color to the texture color
if (max(col.r, max(col.g, col.b)) > 0.75)
{
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
else
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
]]

``````
• Posts: 3,297

Very Nice @juaxix however, individual drops have weird behavior when they merge/separate...

• Posts: 437

Yes, we can try with joints each two balls, but it could be weird too, maybe a little noise for every single drop alone what do you think @Jmv38 ?

• Posts: 3,297

Well, must admit i havent understood how your code works. I have seen that when 2 unit balls touch each other they are transformed in a single ball too much bigger. The ideal would be to keep the surface constant (ball1+ball2+junction surface = constant), or better the volume constant, but this is easier to specify it than to code it!

• edited July 2013 Posts: 437

The code is easy to understand, look, the balls are just physic bodies generated with parameters to give them the behaviour of water drops, it is: restitution, friction, damping, linear velocity, ok, to draw these balls we are using a technique with a shader and a texture, you need a mesh to apply the shader, just like the ripple or other samples, we are setting the width and height of the area of the mesh to the whole screen:

``````m:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
``````

this way we can use the (x,y) position of each ball to paint in the virtual space of the texture (with setContext) of the rect attached to the mesh, so, each ball is represented in the texture painted with the image generated of the ball (200*200) -ballTex -

``````for k,b in ipairs(balls) do
sprite(ballTex, b.x, b.y)
end
``````

then , you have to use the ADDITIVE blend mode to add all the painted balls to the texture and mix with a background.

Correct me if I'm wrong @SkyTheCoder.

We can improve the separated drops with the method from @Zoyt and @spacemonkey shaders, where they numberize and colorize each fragment and use a constant to smooth the draws.

• edited July 2013 Posts: 1,976

@juaxix Yep, that's how it works.

Shortened/simplified version:

In setup you generate a texture, a circle with a gradient, starting at invisible and working its way up to white in the center.

In setup you also create a mesh, the size of the screen, and a separate texture for it. Maybe named "screen."

In setup, one more thing, set the blend mode to ADDITIVE. You have to do this AFTER generating the gradient texture, though.

Set the context (setContext(screen)) to the image you created above. You can then use a mesh or sprite() to draw the above texture at the position of every ball.

Set the rendering back to the screen (setContext()), and set the screen-sized mesh's texture to the one we created for it, named screen. The mesh just uses a shader to filter out the gradients. If the brightness is greater than half way, it renders it white. If it isn't, it's transparent.

Haha, that's not too much shorter...

• Posts: 1,976

One thing I forgot to mention: The reason it acts as Metaballs is because, with the blend mode ADDITIVE, when two gradient circles merge, the darker parts blend together to qualify as light enough to render white. And the reason why it goes from transparent to white instead of black to white is because if it was black to white I think with the blend mode ADDITIVE it would actually darken the brighter parts on the other gradient circle.

• edited July 2013 Posts: 3,297

Thank you for your explanations @juaxix and @skythecoder now i understand it. So here is a very little tweek that will make your balls fusion / separation more reallistic, with no extra cost:

``````        -- replace lines 36-39 by this:
-- with this gaussian shape of the ball texture, the merge will look more reallistic
-- also replace the threshold of 0.75 in the shader by 0.5: more physical look.
local a,d2,ref2
ref2 = ms*ms/15
for i = 1,200 do for j =1,200 do
d2 = (i-100)*(i-100) + (j-100)*(j-100)
a = math.exp(-d2/ref2)*255
ballTex:set(i,j,color(a,a,a,255))
end end
``````
• Posts: 437

.@Jmv38 this water Looks awesome Smooth thanks

• Posts: 3,297

I tried to add at the end of setup:

``````   physics.gravity(Gravity)
``````

but this slows down the fps by x5! And it doesnt seem to work. Any idea of what is wrong?

• edited July 2013 Posts: 437

That's weird...you have to set physics.gravity(Gravity) in the draw() function, because it is updated each frame I'm getting the same FPS value...it might not change anything...

• edited July 2013 Posts: 3,297

@juaxix i've put it in the draw loop and now direction is ok when i tilt the ipad (of course, silly me!) and the FPS is ok too. => works fine, you are correct.

• Posts: 455

Very nice. I didn't get time to look back at this, but I think the 2 pass method is much better for real world applications, especially as then you can just worry about coloring once in the final pass. The whole get the graphics processor to do it visually rather than mathematically is sooo nice.

Pity that won't work for 3d metaballs ;-)

• edited December 2013 Posts: 437

Dont be bad @spacemonkey You are right, we need another 3d metaball shader...i am studying a new way of doing this in 3D. And to improve the method I'm using with physics...

This could be interesting to implement with mesh API:
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch07.html

but the real thing would be to code a Runge Kutta 4 (RK2+RK2):
http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

I'm mixing some terrain generation over a sphere with 3d...
from this:
https://pbs.twimg.com/media/BSsGJUyCEAEE9JY.jpg:large
,to this:
https://pbs.twimg.com/media/BSsGPW5CcAI-Xnb.jpg:large

but if I use normals ( http://www.blackpawn.com/texts/metanormals/ ) it does not work...I would like to mix with metaballs so you can have terrains and 3d water mixed.

Ah, I have found the @SkyTheCoder method in this forum:
https://love2d.org/forums/viewtopic.php?f=5&t=9061
so, no copyright for him as it is copied hehe

• Posts: 226

Ah! Runge-Kutta 4th order is music to my ears... :-B

@juaxix, I'm glad you are moving in the direction I'd like to be going myself too: towards simulation of real physical phenomena, like fluids flow, with games' physics engines... 8-> pero me subí tarde al tren (I don't know how to translate this) (

I look forward to seeing where you get to. Good luck!