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)