using System.Numerics;
using System.Runtime.InteropServices;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Rendering;
/// <summary>Original FocusRing64-byte instance, not a Territory billboard.</summary>
[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<ay)angle=MathF.PI*.5f-angle;if(x<0)angle-=MathF.PI;
if(MathF.Min(x,y)<0&&MathF.Max(x,y)>=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;
}
/// <summary>Source-space shader reference. Emissive is an explicit known
/// input; this helper does not infer the game's dynamic exposure texture.</summary>
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);
}
}
using System.Numerics;
using System.Runtime.InteropServices;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Rendering;
/// <summary>Original FocusRing64-byte instance, not a Territory billboard.</summary>
[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<ay)angle=MathF.PI*.5f-angle;if(x<0)angle-=MathF.PI;
if(MathF.Min(x,y)<0&&MathF.Max(x,y)>=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;
}
/// <summary>Source-space shader reference. Emissive is an explicit known
/// input; this helper does not infer the game's dynamic exposure texture.</summary>
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);
}
}