Utility/Tracer.cs
public sealed class Tracer : Renderer, Component.ExecuteInEditor, Component.ITemporaryEffect
{
[Header( "Position" )]
[Property, Feature( "Tracer" )] public WorldPoint EndPoint { get; set; }
[Header( "Speed" )]
[Property, Feature( "Tracer" )] public float DistancePerSecond { get; set; } = 1.0f;
[Property, Feature( "Tracer" )] public float Length { get; set; } = 100.0f;
[Property, Feature( "Tracer" )] public float StartDistance { get; set; } = 200.0f;
[Header( "Rendering" )]
[Property, Feature( "Tracer" )] public Gradient LineColor { get; set; } = new Gradient( new Gradient.ColorFrame( 0, Color.White ), new Gradient.ColorFrame( 1, Color.White.WithAlpha( 0 ) ) );
[Property, Feature( "Tracer" )] public Curve LineWidth { get; set; } = new Curve( new Curve.Frame( 0, 2 ), new Curve.Frame( 1, 0 ) );
[Property, Feature( "Tracer" )] public SceneLineObject.CapStyle StartCap { get; set; }
[Property, Feature( "Tracer" )] public SceneLineObject.CapStyle EndCap { get; set; }
[Group( "Rendering" )]
[Property] public bool Opaque { get; set; } = true;
[Group( "Rendering" )]
[Property] public bool CastShadows { get; set; } = true;
[Property, FeatureEnabled( "Light", Icon = "💡" )]
public bool EnableLight { get; set; }
[Property, Feature( "Light" )]
public Gradient LightColor { get; set; } = new Gradient( new Gradient.ColorFrame( 0, Color.White ), new Gradient.ColorFrame( 1, Color.White ) );
[Property, Feature( "Light" )]
public Curve LightRadius { get; set; } = new Curve( new Curve.Frame( 0, 128 ), new Curve.Frame( 0.5f, 256 ), new Curve.Frame( 1, 128 ) );
[Property, Feature( "Light" ), Range( 0, 1 )]
public float LightPosition { get; set; } = 0;
[Property] public Material Material { get; set; }
bool ITemporaryEffect.IsActive => !_finished;
float _distance = 0.0f;
bool _finished = false;
private Material _defaultMaterial;
SceneLineObject _so;
SceneLight _light;
protected override void OnEnabled()
{
_so = new SceneLineObject( Scene.SceneWorld );
_so.Transform = Transform.World;
// Legacy support for old texture based renderers
_defaultMaterial = Material.Load( "materials/default/default_line.vmat" ).CreateCopy();
_distance = StartDistance;
}
protected override void OnDisabled()
{
_so?.Delete();
_so = null;
_light?.Delete();
_light = null;
}
protected override void OnUpdate()
{
var len = WorldPosition.Distance( EndPoint.Get() );
_distance += DistancePerSecond * Time.Delta;
if ( _distance > len + Length )
{
_finished = true;
if ( Scene.IsEditor )
_distance = 0;
}
}
protected override void OnPreRender()
{
if ( !_so.IsValid() )
return;
var travel = EndPoint.Get() - WorldPosition;
var maxlen = travel.Length;
if ( _distance - Length > maxlen )
{
_so.RenderingEnabled = false;
_light?.Delete();
_light = null;
return;
}
var midDistance = (_distance - Length * 0.5f).Clamp( 0, maxlen );
var delta = midDistance.LerpInverse( 0, maxlen );
if ( EnableLight )
{
_light ??= new SceneLight( Scene.SceneWorld );
_light.Transform = Transform.World;
_light.QuadraticAttenuation = 10;
_light.LightColor = LightColor.Evaluate( delta );
_light.ShadowsEnabled = false;
_light.Radius = LightRadius.Evaluate( delta );
_light.Position = WorldPosition + travel.Normal * (_distance - Length * LightPosition).Clamp( 0, maxlen - 5 );
}
else
{
_light?.Delete();
_light = null;
}
_so.StartCap = StartCap;
_so.EndCap = EndCap;
_so.Opaque = Opaque;
_so.RenderingEnabled = true;
_so.Transform = WorldTransform;
_so.Flags.CastShadows = CastShadows;
if ( Material.IsValid() )
{
_so.Material = Material;
}
else
{
_defaultMaterial.Set( "Color", Texture.White );
_so.Material = _defaultMaterial;
}
_so.Attributes.SetCombo( "D_BLEND", Opaque ? 0 : 1 );
_so.StartLine();
for ( float x = 0; x <= 1.0f; x += 0.1f )
{
var s = (_distance - Length * x).Clamp( 0, maxlen );
var lineStart = (_distance - maxlen).Clamp( 0, Length ) / Length;
_so.AddLinePoint( WorldPosition + travel.Normal * s, LineColor.Evaluate( x ), LineWidth.Evaluate( x ) );
}
{
var s = (_distance - Length).Clamp( 0, maxlen );
var lineStart = (_distance).Clamp( 0, Length ) / Length;
}
_so.EndLine();
}
}
public struct WorldPoint
{
[KeyProperty]
public Vector3 Origin { get; set; }
public GameObject Parent { get; set; }
public Vector3 Get()
{
if ( Parent.IsValid() )
return Parent.WorldTransform.PointToWorld( Origin );
return Origin;
}
public static implicit operator WorldPoint( Vector3 v )
{
return new WorldPoint { Origin = v };
}
}