A rendering helper component for the BlockParty game. It rasterizes a grapple rope as a 1-pixel-wide Bresenham line on the logical game-pixel grid, emits each pixel as two triangles into a vertex list, and draws that list in a SceneCustomObject render override on the depth-carrying overlay layer at a depth converted from an integer layer index.
using System.Collections.Generic;
using Sandbox;
namespace BlockParty;
/// <summary>Draws a one-pixel Bresenham line directly on the logical game-pixel grid.</summary>
public sealed class GrappleLineRenderer : Component
{
static readonly Material Material = Material.FromShader( "shaders/grapple_line.shader" );
const float ENDPOINT_INSET = 0.001f;
readonly List<Vertex> _vertices = new( Arena.WIDTH * 6 );
SceneCustomObject _sceneObject;
protected override void OnEnabled()
{
_sceneObject = new SceneCustomObject( Scene.SceneWorld )
{
Transform = global::Transform.Zero,
RenderLayer = SceneRenderLayer.OverlayWithDepth,
RenderOverride = OnRender,
};
_sceneObject.Flags.IsOpaque = false;
_sceneObject.Flags.IsTranslucent = true;
_sceneObject.Flags.CastShadows = false;
}
protected override void OnDisabled()
{
_sceneObject?.Delete();
_sceneObject = null;
}
public void SetLine( Vector2 start, Vector2 end, Color color, int depth )
{
_vertices.Clear();
if ( _sceneObject is null ) return;
Vector2 delta = end - start;
float length = delta.Length;
if ( length <= ENDPOINT_INSET * 2f ) return;
// Exact integer endpoints lie between two logical pixels. Select the cells inside the segment
// so opposite directions neither overlap the player nor disappear beneath the hit surface.
Vector2 inset = delta / length * ENDPOINT_INSET;
Vector2 rasterStart = start + inset;
Vector2 rasterEnd = end - inset;
int x = (int)MathF.Floor( rasterStart.x );
int y = (int)MathF.Floor( rasterStart.y );
int endX = (int)MathF.Floor( rasterEnd.x );
int endY = (int)MathF.Floor( rasterEnd.y );
int deltaX = Math.Abs( endX - x );
int stepX = x < endX ? 1 : -1;
int deltaY = -Math.Abs( endY - y );
int stepY = y < endY ? 1 : -1;
int error = deltaX + deltaY;
float z = Globals.DepthToZ( depth );
while ( true )
{
AddPixel( x, y, z, color );
if ( x == endX && y == endY ) break;
int doubledError = error * 2;
if ( doubledError >= deltaY )
{
error += deltaY;
x += stepX;
}
if ( doubledError <= deltaX )
{
error += deltaX;
y += stepY;
}
}
}
public void Clear() => _vertices.Clear();
void AddPixel( int x, int y, float z, Color color )
{
Vertex V( float px, float py ) => new( new Vector3( px, py, z ), color );
_vertices.Add( V( x, y ) );
_vertices.Add( V( x + 1f, y ) );
_vertices.Add( V( x + 1f, y + 1f ) );
_vertices.Add( V( x, y ) );
_vertices.Add( V( x + 1f, y + 1f ) );
_vertices.Add( V( x, y + 1f ) );
}
void OnRender( SceneObject sceneObject )
{
if ( _vertices.Count == 0 || Material is null ) return;
Graphics.Draw( _vertices, _vertices.Count, Material, primitiveType: Graphics.PrimitiveType.Triangles );
}
}