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)

Más ejemplos de Procedural