Skip to content

Instantly share code, notes, and snippets.

@atonamy
Created June 6, 2021 05:48
Show Gist options
  • Save atonamy/0f0b9bac89e152f6dcbe70f3f411a42d to your computer and use it in GitHub Desktop.
Save atonamy/0f0b9bac89e152f6dcbe70f3f411a42d to your computer and use it in GitHub Desktop.
2D Metaballs implementation in Godot
#based on this article http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/
extends Node2D
class Blob:
var pos_x
var pos_y
var radius
var velocity
func _init(x, y, r, v):
pos_x = x
pos_y = y
radius = r
velocity = v
const cell_size = 16
const blobs_count = 10
const blob_size = [20, 40]
var screen_size
var blobs
var allowUpdate = true
#var font = DynamicFont.new()
const drawMap = {
0: null,
1: [-0.5, 0, 0, -0.5],
2: [-0.5, -1, 0, -0.5],
3: [-0.5, 0, -0.5, -1],
4: [-1, -0.5, -0.5, -1],
5: [-1, -0.5, -0.5, 0, -0.5, -1, 0, -0.5],
6: [-1, -0.5, 0, -0.5],
7: [-1, -0.5, -0.5, 0],
8: [-1, -0.5, -0.5, 0],
9: [-1, -0.5, 0, -0.5],
10: [-1, -0.5, -0.5, -1, -0.5, 0, 0, -0.5],
11: [-1, -0.5, -0.5, -1],
12: [-0.5, -1, -0.5, 0],
13: [-0.5, -1, 0, -0.5],
14: [-0.5, 0, 0, -0.5],
15: null
}
func calcIsoSurface(x1, x2, y1, y2, r):
var dx = abs(x1 - x2)
var dy = abs(y1 - y2)
var sd = dx*dx + dy*dy
var res = float(r*r) / float(sd)
return res
# Called when the node enters the scene tree for the first time.
func _ready():
#font.font_data = load("res://font.ttf")
#font.size = 7
screen_size = get_viewport().size
blobs = Array()
var rng = RandomNumberGenerator.new()
var r = rng.randi_range(blob_size[0], blob_size[1])
var x = rng.randi_range(r, screen_size.x - r)
var y = rng.randi_range(r, screen_size.y - r)
for n in range(blobs_count):
blobs.push_back(
Blob.new(
x,
y,
r,
Vector2(rng.randf_range(-3, 3), rng.randf_range(-3, 3))
)
)
print(screen_size)
func formDrawIndex(x, y, sum, vertexes):
var drawIndex = 0
var corners = []
if x > 0 && y > 0:
var vertex = vertexes.pop_front()
if sum >= 1:
drawIndex |= 1
if vertexes.back() >= 1:
drawIndex |= 2
if vertex >= 1:
drawIndex |= 4
if vertexes.front() >= 1:
drawIndex |= 8
corners.push_back(sum)
corners.push_back(vertexes.back())
corners.push_back(vertex)
corners.push_back(vertexes.front())
return {"draw_index": drawIndex, "corners": corners}
func exLerp(oneSum, zeroSum):
if oneSum == zeroSum:
return null
return -(1-((1 - oneSum) / (zeroSum - oneSum)))
func interpolateLines(lines, corners):
if lines == null:
return lines
for i in range(0, lines.size(), 2):
var x = lines[i]
var y = lines[i+1]
if (x == 0 || x == -1) && (y == 0 || y == -1):
continue
if (x == 0 || x == -1):
lines[i+1] = exLerp(corners[1], corners[0]) if x == 0 else exLerp(corners[2], corners[3])
if (y == 0 || y == -1):
lines[i] = exLerp(corners[3], corners[0]) if y == 0 else exLerp(corners[2], corners[1])
return lines
func drawLines(x, y, lines):
if lines != null && lines.size() >= 4:
draw_line(
Vector2(x + (cell_size*lines[0]), y + (cell_size*lines[1])),
Vector2(x + (cell_size*lines[2]), y + (cell_size*lines[3])),
Color.green
)
if lines != null && lines.size() == 8:
draw_line(
Vector2(x + (cell_size*lines[4]), y + (cell_size*lines[5])),
Vector2(x + (cell_size*lines[6]), y + (cell_size*lines[7])),
Color.green
)
# Called after update() in the _process()
func _draw():
var vertexes = []
for x in range(0, screen_size.x, cell_size):
for y in range(0, screen_size.y, cell_size):
var sum = 0
for blob in blobs:
sum += calcIsoSurface(x, blob.pos_x, y, blob.pos_y, blob.radius)
#var c = Color.blue
#c.a = 0.0001
#draw_circle(Vector2(blob.pos_x,blob.pos_y), blob.radius, c)
#if sum >= 1:
# draw_rect(Rect2(x, y, 1, 1), Color.red)
#else:
# draw_rect(Rect2(x, y, 1, 1), Color.black)
#draw_string(font, Vector2(x, y), "%s" % sum, Color.black)
var indexies = formDrawIndex(x, y, sum, vertexes)
var lines = drawMap[indexies["draw_index"]]
var corners = indexies["corners"]
lines = interpolateLines(lines, corners)
drawLines(x, y, lines)
vertexes.push_back(sum)
if x > 0:
vertexes.pop_front()
func _input(event):
if event is InputEventMouseButton && event.is_pressed():
allowUpdate = !allowUpdate
print(allowUpdate)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if !allowUpdate:
return
update()
for blob in blobs:
blob.pos_x += blob.velocity.x
blob.pos_y += blob.velocity.y
if blob.pos_x > screen_size.x || blob.pos_x < 0:
blob.velocity.x *= -1
if blob.pos_y > screen_size.y || blob.pos_y < 0:
blob.velocity.y *= -1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment