Planet.gd calls visual.configure() BEFORE add_child(), and the SceneTree defers _ready() to the first frame - so configure() built the five rings, and _ready() then threw them away and built five more. Eighty antialiased polylines tessellated per black hole instead of forty, plus five nodes queued for free. Confirmed by instrumenting _build(): _build() called, existing children=0 <- configure() _build() called, existing children=5 <- _ready() _ready() now uses the same is-it-built guard configure() already had. Note for anyone chasing the same thing: this is NOT the disc's steady-state cost. Measured, the geometry rasterises zero times over sixty frames (the NeonPiece refactor works), and rendering the disc costs 0.1 ms against a 133 ms softpipe floor. The disc is cheap once it exists. 172/172. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
79 lines
3 KiB
GDScript
79 lines
3 KiB
GDScript
extends Node2D
|
|
|
|
# Same informal interface as GasGiantVisual/RockyPlanetVisual - configure(),
|
|
# extractor_anchor(), animates via its own _process(). No solid body: the
|
|
# core stays genuinely empty (nothing drawn inside CORE_RADIUS), and an
|
|
# accretion disc of rotating arc segments - not closed rings - stands in
|
|
# for a surface, brighter and thicker toward the centre.
|
|
#
|
|
# Each ring is a NeonPiece that is SPUN, not redrawn. Issues #6 and #10 made
|
|
# that the rule for the buildings and it applies here more than anywhere: the
|
|
# disc was ten glowing paths of twenty-five points re-tessellated every frame
|
|
# - forty antialiased polylines - to turn five rings at five different rates.
|
|
# Rotation is a node property, so now nothing is rasterised after the first
|
|
# frame and the disc turns for free.
|
|
|
|
var disc_phase: float = 0.0
|
|
|
|
const CORE_RADIUS: float = 46.0
|
|
const RING_COUNT: int = 5
|
|
const RING_STEP: float = 16.0
|
|
const DISC_COLOR: Color = Color(0.55, 0.15, 0.85)
|
|
const DISC_HOT_COLOR: Color = Color(0.9, 0.6, 1.0)
|
|
|
|
# One per ring, with the rate and direction it turns at.
|
|
var _rings: Array = []
|
|
|
|
|
|
func _ready() -> void:
|
|
# Planet.gd calls configure() BEFORE add_child(), so the disc is usually
|
|
# already built by the time _ready fires (which the SceneTree defers to
|
|
# the first frame). Without this guard the ring set was thrown away and
|
|
# re-tessellated - eighty antialiased polylines per spawn instead of forty.
|
|
if _rings.is_empty():
|
|
_build()
|
|
|
|
|
|
func configure(_shape_seed: int) -> void:
|
|
if _rings.is_empty():
|
|
_build()
|
|
|
|
|
|
func extractor_anchor() -> Vector2:
|
|
return Vector2(0.0, -(CORE_RADIUS + float(RING_COUNT - 1) * RING_STEP + 12.0))
|
|
|
|
|
|
func _build() -> void:
|
|
for child in get_children():
|
|
child.queue_free()
|
|
_rings.clear()
|
|
for i in range(RING_COUNT):
|
|
var radius: float = CORE_RADIUS + float(i) * RING_STEP
|
|
# 1.0 at the innermost ring, fading to 0.0 at the outermost - drives
|
|
# both colour (hotter inward) and stroke weight/brightness.
|
|
var t: float = 1.0 - float(i) / float(maxi(RING_COUNT - 1, 1))
|
|
var piece: NeonPiece = NeonPiece.create(self, Vector2.ZERO,
|
|
DISC_COLOR.lerp(DISC_HOT_COLOR, t * 0.65))
|
|
# The two arcs are built at their resting angles; the ring node turns
|
|
# them. Alternating direction per ring and a speed tied to t reads as
|
|
# genuine turbulence rather than one disc rotating as a rigid whole.
|
|
for arc_i in range(2):
|
|
var start: float = float(arc_i) * PI + 0.15
|
|
var end: float = start + PI - 0.3
|
|
var points := PackedVector2Array()
|
|
var steps: int = 24
|
|
for j in range(steps + 1):
|
|
var angle: float = lerpf(start, end, float(j) / float(steps))
|
|
points.append(Vector2(cos(angle), sin(angle)) * radius)
|
|
piece.add_path(points, 0.5 + t * 0.5, 0.45 + t * 0.35)
|
|
_rings.append({
|
|
"piece": piece,
|
|
"rate": (0.12 + t * 0.22) * (1.0 if i % 2 == 0 else -1.0),
|
|
})
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
disc_phase += delta
|
|
# One property per ring. Nothing is rasterised again.
|
|
for ring in _rings:
|
|
(ring.piece as NeonPiece).rotation = disc_phase * float(ring.rate)
|