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.
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();
}
}