Marching squares
The contour of a noise field, redrawn as the threshold sweeps. The field never changes, only the cut.
The editor above is running this project. Change a line and it reloads.
Godot classes used
Node2DFastNoiseLiteLabel
The code
scripts/main.gd
extends Node
const COLUMNS: int = 32
const ROWS: int = 18
const SEED: int = 20260814
# marching squares case table, each pair of edges is one segment
const CASES: Array = [
[], [3, 0], [0, 1], [3, 1], [1, 2], [3, 0, 1, 2], [0, 2], [3, 2],
[2, 3], [2, 0], [0, 1, 2, 3], [2, 1], [1, 3], [1, 0], [0, 3], [],
]
var samples: PackedFloat32Array = PackedFloat32Array()
var canvas: Node2D
var label: Label
var origin: Vector2
var cell: float
var threshold: float = 0.5
var elapsed: float = 0.0
func _ready() -> void:
_add_background()
var noise: FastNoiseLite = FastNoiseLite.new()
noise.seed = SEED
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
noise.fractal_octaves = 3
noise.frequency = 0.09
for y in ROWS + 1:
for x in COLUMNS + 1:
samples.append(noise.get_noise_2d(x, y) * 0.5 + 0.5)
var view: Vector2 = get_viewport().get_visible_rect().size
cell = minf(view.x / COLUMNS, view.y / ROWS)
origin = (view - Vector2(COLUMNS, ROWS) * cell) * 0.5
canvas = Node2D.new()
# draw_line only works while the canvas item is drawing
canvas.draw.connect(_redraw)
add_child(canvas)
label = Label.new()
label.position = Vector2(40, 32)
label.add_theme_color_override('font_color', Color('#e6ecff'))
label.add_theme_color_override('font_shadow_color', Color('#12141a'))
label.add_theme_constant_override('shadow_offset_y', 2)
add_child(label)
print('the field never changes, only the threshold the contour follows')
func _process(delta: float) -> void:
elapsed += delta
threshold = 0.5 + sin(elapsed * 0.6) * 0.16
label.text = 'threshold %.3f' % threshold
canvas.queue_redraw()
func _redraw() -> void:
for y in ROWS + 1:
for x in COLUMNS + 1:
var inside: bool = _sample(x, y) > threshold
canvas.draw_circle(_point(x, y), 4.0 if inside else 2.5, Color('#8da5f3') if inside else Color('#39415a'))
for y in ROWS:
for x in COLUMNS:
var pattern: int = 0
pattern |= 1 if _sample(x, y) > threshold else 0
pattern |= 2 if _sample(x + 1, y) > threshold else 0
pattern |= 4 if _sample(x + 1, y + 1) > threshold else 0
pattern |= 8 if _sample(x, y + 1) > threshold else 0
var edges: Array = CASES[pattern]
for i in range(0, edges.size(), 2):
canvas.draw_line(_edge_point(edges[i], x, y), _edge_point(edges[i + 1], x, y), Color('#8eef97'), 3.0)
func _edge_point(edge: int, x: int, y: int) -> Vector2:
match edge:
0:
return _mix(x, y, x + 1, y)
1:
return _mix(x + 1, y, x + 1, y + 1)
2:
return _mix(x + 1, y + 1, x, y + 1)
return _mix(x, y + 1, x, y)
func _mix(ax: int, ay: int, bx: int, by: int) -> Vector2:
var a: float = _sample(ax, ay)
var b: float = _sample(bx, by)
var weight: float = 0.5 if is_equal_approx(a, b) else clampf(inverse_lerp(a, b, threshold), 0.0, 1.0)
return _point(ax, ay).lerp(_point(bx, by), weight)
func _sample(x: int, y: int) -> float:
return samples[y * (COLUMNS + 1) + x]
func _point(x: int, y: int) -> Vector2:
return origin + Vector2(x, y) * cell
func _add_background() -> void:
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
add_child(background)
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)