using System.Numerics; using System.Runtime.InteropServices; using XFEEDToolBox.Core.Galaxy; using XFEEDToolBox.Core.Models; namespace XFEEDToolBox.Core.Rendering; /// Original FocusRing64-byte instance, not a Territory billboard. [StructLayout(LayoutKind.Sequential)] public readonly record struct GalaxyOriginalPowerActivityRing(Vector4 Position,Vector4 UAxisInner,Vector4 VAxisOuter,Vector4 Colour); public readonly record struct GalaxyOriginalPowerActivityRingVertex(Vector3 World,Vector4 AnnulusRadii,Vector4 Colour); public static class GalaxyOriginalPowerActivityRingGeometry { public const int QuadCount=16,VertexCount=34; public const float SegmentCount=200,RadialFeather=.800000011920929f,SegmentFeather=.300000011920929f,SolidRatio=.850000023841858f; // Original3B33690's range record. Its separate extent/FocusBillboard and // per-system influence links require other original inputs, not this R. public static bool TryCreate(GalaxyNativePowerOuter? bound,Coordinates eye,out GalaxyOriginalPowerActivityRing ring) { ring=default; if(bound is not{CentreSystemAddress:>0,CentrePosition:{} centre,Radius:>0 and <=1000000}||!centre.IsValid||!eye.IsValid)return false; var radius=bound.Radius.Value;var position=new Vector3((float)centre.X,(float)centre.Y,(float)centre.Z); var relative=new Vector3((float)(eye.X-centre.X),(float)(eye.Y-centre.Y),(float)(eye.Z-centre.Z)); var distance=relative.Length();if(!float.IsFinite(distance)||!float.IsFinite(radius))return false; var width=.05f*Math.Clamp(distance/radius,1,6); ring=new(new(position,1),new(1,0,0,radius-width),new(0,0,1,radius+width),new(1,1,1,0));return true; } public static bool IsValid(GalaxyOriginalPowerActivityRing ring)=>Finite(ring.Position)&&Finite(ring.UAxisInner)&&Finite(ring.VAxisOuter)&& Finite(ring.Colour)&&ring.VAxisOuter.W>ring.UAxisInner.W&&ring.VAxisOuter.W>0; private static bool Finite(Vector4 v)=>float.IsFinite(v.X)&&float.IsFinite(v.Y)&&float.IsFinite(v.Z)&&float.IsFinite(v.W); //3C01F30 stores32 primitives.51BE30 uses original type5 conversion(1,2) // to submit34 vertices;5101A0 passes mapped D3D topology5/TriangleStrip. public static GalaxyOriginalPowerActivityRingVertex Vertex(GalaxyOriginalPowerActivityRing ring,int vertex) { if(!IsValid(ring)||vertex is <0 or >=VertexCount)throw new ArgumentOutOfRangeException(nameof(vertex)); var radius=(vertex&1)!=0?ring.UAxisInner.W:ring.VAxisOuter.W/MathF.Cos(MathF.PI/QuadCount); var angle=(vertex>>1)*(2*MathF.PI/QuadCount);var(sine,cosine)=MathF.SinCos(angle); var local=new Vector2(cosine*radius,sine*radius); var world=new Vector3(ring.Position.X,ring.Position.Y,ring.Position.Z)+ new Vector3(ring.UAxisInner.X,ring.UAxisInner.Y,ring.UAxisInner.Z)*local.X+ new Vector3(ring.VAxisOuter.X,ring.VAxisOuter.Y,ring.VAxisOuter.Z)*local.Y; return new(world,new(ring.UAxisInner.W,ring.VAxisOuter.W,local.X,local.Y),ring.Colour); } private static float Fraction(float value)=>value-MathF.Floor(value); private static float Smooth(float value){value=Math.Clamp(value,0,1);return value*value*(3-2*value);} public static float Coverage(Vector4 radii) { var x=radii.Z;var y=radii.W;var r=MathF.Sqrt(x*x+y*y); if(!float.IsFinite(r)||r<=radii.X||r>=radii.Y||radii.Y<=radii.X)return 0; var radial=Smooth((1-2*MathF.Abs((r-radii.X)/(radii.Y-radii.X)-.5f))/RadialFeather); var ax=MathF.Abs(x);var ay=MathF.Abs(y);var q=MathF.Min(ax,ay)/MathF.Max(ax,ay);var q2=q*q; // Original FocusRing PS atan approximation, including quadrant signs. var angle=((((.02083509974181652f*q2-.08513300120830536f)*q2+.18014100193977356f)*q2-.3302994966506958f)*q2+.9998660087585449f)*q; if(ax=0)angle=-angle; var phase=Fraction(Fraction(angle*.159154943091895f)*SegmentCount); var angular=Smooth((2*MathF.Abs(phase-.5f)-(1-SolidRatio))/(SolidRatio*SegmentFeather)); return radial*angular; } /// Source-space shader reference. Emissive is an explicit known /// input; this helper does not infer the game's dynamic exposure texture. public static Vector4 Shade(Vector4 radii,Vector4 colour,float emissiveRescaling,float hdrWrite=1) { if(!float.IsFinite(emissiveRescaling)||emissiveRescaling<0||!float.IsFinite(hdrWrite)||hdrWrite<=0)throw new ArgumentOutOfRangeException(nameof(emissiveRescaling)); return new(new Vector3(colour.X,colour.Y,colour.Z)*Coverage(radii)*emissiveRescaling*hdrWrite,colour.W); } }