LineRingRenderer.cs

A Renderer component that draws a procedural circular ring as a scene line object. It builds a segmented ring in OnPreRender, sets material attributes (texture, blending, fog, depth feather, lighting, shadow flags) and updates line points with per-segment color, width and UVs.

File Access
using System;
using Sandbox;

/// <summary>
/// Renders a procedural ring
/// </summary>
[Title( "Line Ring Renderer" )]
[Category( "Rendering" )]
[Icon( "donut_large" )]
public sealed class LineRingRenderer : Renderer, Component.ExecuteInEditor
{
	SceneLineObject _so;

	[Property, Range( 0.1f, 1000f )] public float Radius { get; set; } = 100f;
	[Property, Range( 3, 128 )] public int Segments { get; set; } = 32;

	[Group( "Appearance" )]
	[Property] public Gradient Color { get; set; } = global::Color.Cyan;
	[Property] public Curve Width { get; set; } = 5;
	[Property, InlineEditor( Label = false )] public TrailTextureConfig Texturing { get; set; } = TrailTextureConfig.Default;

	[Group( "Rendering" )]
	[Property] public bool Wireframe { get; set; }
	[Property] public bool Opaque { get; set; } = true;
	[Property] public bool Additive { get; set; }
	[Property] public bool CastShadows { get; set; } = true;
	[Property] public float DepthFeather { get; set; }
	[Property, Range( 0, 1 )] public float FogStrength { get; set; } = 1.0f;
	[Property] public bool Lighting { get; set; }

	private float scrollTime;

	protected override void OnEnabled()
	{
		if ( Application.IsDedicatedServer )
			return;

		_so = new SceneLineObject( Scene.SceneWorld );
		_so.Transform = WorldTransform;
		_so.RenderingEnabled = false;

		scrollTime = 0;
	}

	protected override void OnDisabled()
	{
		_so?.Delete();
		_so = null;
	}

	protected override void OnPreRender()
	{
		if ( !_so.IsValid() || Segments < 3 )
			return;

		var transform = WorldTransform;

		_so.StartCap = SceneLineObject.CapStyle.Rounded;
		_so.EndCap = SceneLineObject.CapStyle.Rounded;
		_so.Face = SceneLineObject.FaceMode.Normal;
		_so.Wireframe = Wireframe;
		_so.RenderingEnabled = true;
		_so.Transform = transform;
		_so.Flags.CastShadows = CastShadows;
		_so.Attributes.Set( "BaseTexture", Texturing.Texture ?? Texture.White );
		_so.Attributes.Set( "g_DepthFeather", DepthFeather );
		_so.Attributes.Set( "g_FogStrength", FogStrength );
		_so.Attributes.SetCombo( "D_BLEND", Additive ? 1 : 0 );
		_so.Attributes.SetCombo( "D_OPAQUE", Opaque ? 1 : 0 );
		_so.Attributes.SetCombo( "D_ENABLE_LIGHTING", Lighting ? 1 : 0 );
		_so.Attributes.Set( "g_bNonDirectionalDiffuseLighting", true );
		_so.Flags.IsOpaque = Opaque;
		_so.Flags.IsTranslucent = !Opaque;

		RenderOptions.Apply( _so );

		_so.StartLine();

		scrollTime += Time.Delta * Texturing.Scroll;

		float angleStep = 360f / Segments;
		float uv = 0.0f;

		for ( int i = 0; i <= Segments; i++ )
		{
			float angle = MathX.DegreeToRadian( i * angleStep );
			Vector3 pos = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f ) * Radius;
			pos = transform.PointToWorld( pos );
			float t = i / (float)Segments;

			uv = Texturing.WorldSpace
				? (Radius * angle) / (Texturing.UnitsPerTexture > 0 ? Texturing.UnitsPerTexture : 1)
				: t * Texturing.Scale;

			uv += scrollTime + Texturing.Offset;

			_so.AddLinePoint( pos, transform.Up, Color.Evaluate( t ), Width.Evaluate( t ), uv );
		}

		_so.EndLine();
	}
}