Marching squares
El contorno de un campo de ruido, redibujado según barre el umbral. El campo nunca cambia, solo el corte.
El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.
Clases de Godot usadas
Node2DFastNoiseLiteLabel
El código
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)