FootballFloodlightPresentation.cs

One partial of the football stadium panel's evening presentation: builds a single static mesh from generated polygons for floodlight lenses, halos and light shafts, then attaches it through a ModelRenderer. Turf lighting itself comes from four lights elsewhere.

using Sandbox;
using Socer;
using System;
using System.Collections.Generic;
using System.Linq;

public sealed partial class FootballWorldPanel
{
    ModelRenderer eveningLamps;
    Model eveningLampModel;
    int eveningLampBanks,eveningLampVertices;

    // One static alpha batch: lit lenses, small feathered halos and short
    // shafts at the fixtures. Actual pools on the turf come from four lights;
    // there are no painted circles or fullscreen colour/tint overlays.
    void EnsureEveningLampPresentation()
    {
        foreach(var old in RenderScene.GetAllComponents<ModelRenderer>().Where(r=>r.GameObject.Name=="Socer evening lamp presentation").ToArray())
            old.GameObject.Destroy();
        var vertices=new List<Vertex>(4000);
        int banks=0;
        void Triangle(Vector3 a,Vector3 b,Vector3 c,byte aa,byte ab,byte ac)
        {
            vertices.Add(new Vertex(a,new Color32(229,243,255,aa)));
            vertices.Add(new Vertex(b,new Color32(229,243,255,ab)));
            vertices.Add(new Vertex(c,new Color32(229,243,255,ac)));
        }
        void Lens(Vector3 centre,Vector3 horizontal,Vector3 vertical,float width,float height)
        {
            var u=horizontal*(width*.5f*Units);var v=vertical*(height*.5f*Units);
            Triangle(centre-u-v,centre+u-v,centre+u+v,255,255,255);
            Triangle(centre-u-v,centre+u+v,centre-u+v,255,255,255);
        }
        void Halo(Vector3 centre,Vector3 horizontal,Vector3 vertical,float radius,byte alpha)
        {
            for(int i=0;i<16;i++)
            {
                float a=i*MathF.Tau/16,b=(i+1)*MathF.Tau/16;
                var outerA=centre+(horizontal*MathF.Cos(a)+vertical*MathF.Sin(a))*(radius*Units);
                var outerB=centre+(horizontal*MathF.Cos(b)+vertical*MathF.Sin(b))*(radius*Units);
                Triangle(centre,outerA,outerB,alpha,0,0);
            }
        }
        void Shaft(Vector3 from,Vector3 target,float length,float radius,byte alpha)
        {
            var direction=(target-from).Normal;var end=from+direction*(length*Units);
            var across=Vector3.Cross(direction,Vector3.Up).Normal;
            var other=Vector3.Cross(across,direction).Normal;
            foreach(var axis in new[]{across,other})
            {
                // A narrow centre strip with transparent edges avoids a solid
                // triangular panel when the camera pans past the stadium.
                float[] bands={-1,-.25f,.25f,1};
                for(int band=0;band<3;band++)
                {
                    var a=from+axis*(bands[band]*.10f*Units);
                    var b=from+axis*(bands[band+1]*.10f*Units);
                    var c=end+axis*(bands[band+1]*radius*Units);
                    var d=end+axis*(bands[band]*radius*Units);
                    byte lo=band==0?(byte)0:alpha,hi=band==2?(byte)0:alpha;
                    Triangle(a,b,c,lo,hi,0);Triangle(a,c,d,lo,0,0);
                }
            }
        }
        for(int i=0;i<4;i++)
        {
            banks++;
            bool east=(i&1)!=0,south=(i&2)!=0;
            var mast=new Point(east?115:-10,south?76:-8);
            for(int col=0;col<5;col++)for(int row=0;row<2;row++)
            {
                var at=WorldPoint(mast+new Point((col-2)*.72f,south?-.375f:.375f),21.55f+row*.85f);
                Lens(at,Vector3.Forward,Vector3.Up,.48f,.52f);
            }
            var centre=WorldPoint(mast+new Point(0,south?-.39f:.39f),22);
            Halo(centre,Vector3.Forward,Vector3.Up,1.35f,45);
            Shaft(centre,WorldPoint(new Point(east?82:23,south?47:21),0),11,1.0f,17);
        }
        foreach(float x in new[]{6f,19,32,45,60,73,86,99})
        {
            banks++;
            var centre=WorldPoint(new Point(x,-13.83f),7.71f);
            for(int lens=0;lens<3;lens++)
                Lens(centre+Vector3.Forward*((lens-1)*.24f*Units),Vector3.Forward,Vector3.Up,.22f,.25f);
            Halo(centre,Vector3.Forward,Vector3.Up,.8f,65);
            Shaft(centre,WorldPoint(new Point(x,-3.5f),0),5.2f,.54f,22);
        }
        var bounds=new BBox(WorldPoint(new Point(-13,79),0),WorldPoint(new Point(118,-22),24));
        var mesh=new Mesh(DynamicGeometryMaterial);mesh.CreateVertexBuffer(vertices.Count,vertices);mesh.Bounds=bounds;
        eveningLampModel=Model.Builder.AddMesh(mesh).WithViewBounds(bounds).Create();
        eveningLamps=Create(eveningLampModel,"Socer evening lamp presentation");
        eveningLamps.Tint=Color.White;eveningLamps.RenderType=ModelRenderer.ShadowRenderType.Off;
        eveningLampBanks=banks;eveningLampVertices=vertices.Count;
    }
}