FootballSidelineSurface.cs

Builds the shared painted technical-area surface mesh for the football pitch, placing two quads per team bench and trimming them clear of the turf runoff strip. Also exposes SidelineSurfaceReport, an anonymous snapshot of surface geometry, gap and paint measurements with fine-stroke counts.

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

public sealed partial class FootballWorldPanel
{
    [SkipHotload] static Model sharedTechnicalSurface;
    [SkipHotload] static int sharedTechnicalSurfaceVersion;
    ModelRenderer technicalSurface;
    int technicalSurfaceVersion;
    const int TechnicalSurfaceGeometryVersion=2;
    const float TechnicalSurfaceBottomY=-9.8f;
    const float TechnicalSurfaceFrontY=-1.1f;
    const float TurfRunoffFrontY=-1f;
    const float TechnicalSurfaceHalfWidth=7.8f;
    const float TechnicalPaintFrontY=-1.25f;
    const float TechnicalPaintWidth=.14f;

    void InitializeTechnicalSurface()
    {
        if(authoredEnvironment!=null)
        {
            technicalSurface=authoredEnvironment.GameObject.GetComponentsInChildren<ModelRenderer>().FirstOrDefault(r=>r.GameObject.Tags.Has("football-technical-surface"));
            technicalSurfaceVersion=TechnicalSurfaceGeometryVersion;return;
        }
        if(technicalSurface!=null && technicalSurfaceVersion==TechnicalSurfaceGeometryVersion)return;
        if(technicalSurface!=null){technicalSurface.GameObject.Destroy();technicalSurface=null;}
        if(sharedTechnicalSurface==null || sharedTechnicalSurfaceVersion!=TechnicalSurfaceGeometryVersion)
        {
            var vertices=new List<Vertex>(12);
            Vertex V(Point at,Vector2 uv)=>new(WorldPoint(at),Vector3.Up,Vector3.Forward,uv);
            for(int team=0;team<2;team++)
            {
                float x=TechnicalAreaPresentation.BenchX(team);
                // The turf includes a one-metre runoff strip. Two opaque
                // coplanar surfaces formerly overlapped there, including half
                // of every front dash. Trim the platform clear of that strip;
                // its paint is part of this sole top face, never a depth overlay.
                var a=new Point(x-TechnicalSurfaceHalfWidth,TechnicalSurfaceBottomY);
                var b=new Point(x+TechnicalSurfaceHalfWidth,TechnicalSurfaceBottomY);
                var c=new Point(x+TechnicalSurfaceHalfWidth,TechnicalSurfaceFrontY);
                var d=new Point(x-TechnicalSurfaceHalfWidth,TechnicalSurfaceFrontY);
                vertices.Add(V(a,new(0,0)));vertices.Add(V(c,new(1,1)));vertices.Add(V(b,new(1,0)));
                vertices.Add(V(a,new(0,0)));vertices.Add(V(d,new(0,1)));vertices.Add(V(c,new(1,1)));
            }
            var bounds=new BBox(WorldPoint(new Point(20,TechnicalSurfaceFrontY),-.02f),
                WorldPoint(new Point(85,TechnicalSurfaceBottomY),.02f));
            var mesh=new Mesh(Material.Load("materials/pitch/technical-area-painted.vmat"));
            mesh.CreateVertexBuffer(vertices.Count,vertices);mesh.Bounds=bounds;
            sharedTechnicalSurface=Model.Builder.AddMesh(mesh).WithViewBounds(bounds).Create();
            environmentSourceVertices[sharedTechnicalSurface]=vertices.ToArray();
            sharedTechnicalSurfaceVersion=TechnicalSurfaceGeometryVersion;
        }
        technicalSurface=Create(sharedTechnicalSurface,"Technical-area painted surface");
        technicalSurface.RenderType=ModelRenderer.ShadowRenderType.Off;
        technicalSurfaceVersion=TechnicalSurfaceGeometryVersion;
    }

    int SidelineGroundStrokeCount()
    {
        int count=0;
        foreach(var stroke in fineStadiumStrokes)
            if(Math.Max(stroke.ZA,stroke.ZB)<=.03f)count++;
        return count;
    }

    public object SidelineSurfaceReport()=>new
    {
        PaintedSurface=technicalSurface!=null,
        PaintedQuads=technicalSurface!=null?2:0,
        PaintedVertices=technicalSurface!=null?12:0,
        GeometryVersion=technicalSurfaceVersion,SharedGeometryVersion=sharedTechnicalSurfaceVersion,
        OpaquePaint=true,
        SurfaceBottomY=TechnicalSurfaceBottomY,SurfaceFrontY=TechnicalSurfaceFrontY,
        TurfFrontY=TurfRunoffFrontY,
        SurfaceGapFromTurfMetres=TurfRunoffFrontY-TechnicalSurfaceFrontY,
        PaintedTurfOverlapSquareMetres=2*TechnicalSurfaceHalfWidth*2*Math.Max(0,TechnicalSurfaceFrontY-TurfRunoffFrontY),
        CoplanarOverlay=TechnicalSurfaceFrontY>TurfRunoffFrontY,
        PaintFrontY=TechnicalPaintFrontY,PaintWidthMetres=TechnicalPaintWidth,
        PaintGapFromTurfMetres=TurfRunoffFrontY-(TechnicalPaintFrontY+TechnicalPaintWidth*.5f),
        FineStadiumGroundStrokes=SidelineGroundStrokeCount(),
        DashBoxes=0,FineStrokes=fineStadiumStrokes.Count,
        FineStrokeVertices=fineStadiumStrokes.Count*18,
        SharedWithGoalStrokeBatch=true,StrokeCapacity=goalStrokeVertices.Length,
        CurrentStrokeVertices=goalStrokeVertexCount,
        Texture="textures/pitch/technical-area-painted-v1.png"
    };
}