using System.Numerics;
using System.Runtime.InteropServices;
using System.Text.Json;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using Vortice.Mathematics;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Rendering;
using static Vortice.Direct3D11.D3D11;
const int width=1600,height=900;
D3D11CreateDevice(IntPtr.Zero,DriverType.Warp,DeviceCreationFlags.BgraSupport,[FeatureLevel.Level_11_0],out var device,out _,out var context).CheckError();
using var d=device;using var c=context;using var renderer=new GalaxyGraphics(device,context);
using var texture=device.CreateTexture2D(Format.B8G8R8A8_UNorm,width,height,mipLevels:1,bindFlags:BindFlags.RenderTarget);
using var target=device.CreateRenderTargetView(texture);
using var rasterizer=device.CreateRasterizerState(new RasterizerDescription(CullMode.None,FillMode.Solid));
using var noDepth=device.CreateDepthStencilState(new DepthStencilDescription(false,DepthWriteMask.Zero,ComparisonFunction.Always));
context.OMSetRenderTargets(target);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);context.OMSetDepthStencilState(noDepth);
var output=Path.GetFullPath("artifacts/galaxy-trade-shader-validation");Directory.CreateDirectory(output);var reports=new List<object>();
var flows=Enumerable.Range(0,13).Select(i=>
{
var from=new GalacticSystem("Synthetic from",new(-8,(i-6)*1.5,0),9007199254740993+i,"G");
var to=new GalacticSystem("Synthetic to",new(8,(i-6)*1.5,2),9007199254741993+i,"G");
return new GalaxyCurrentTradeRenderFlow(new(from.SystemAddress!.Value,to.SystemAddress!.Value,i,.025f+i*.05f,[]),from,to);
}).ToArray();
renderer.SetCurrentTradeFlows(flows);
foreach(var (name,yaw,pitch,scale) in new[]{("front",0d,0d,20d),("tilted",.3,.4,16d),("oblique",-.5,-.3,25d)})
{
var camera=GalaxyCamera.Create(new(0,0,0),width,height,yaw,pitch,scale);
renderer.Prepare(camera);
foreach(var hotspot in new[]{false,true})
{
context.ClearRenderTargetView(target,new Color4(0,0,0,1));renderer.DrawPort(hotspot);var port=Read();
context.ClearRenderTargetView(target,new Color4(0,0,0,1));renderer.DrawOriginal(camera,hotspot);var original=Read();
var key=name+(hotspot?"-hotspots":"-routes");File.WriteAllBytes(Path.Combine(output,key+".bgra"),port);File.WriteAllBytes(Path.Combine(output,key+"-original.bgra"),original);
long difference=0;var divergent=0;var lit=0;
for(var i=0;i<port.Length;i+=4)
{
var delta=0;for(var channel=0;channel<3;channel++)delta=Math.Max(delta,Math.Abs(port[i+channel]-original[i+channel]));
difference+=delta;if(delta>3)divergent++;
if(Math.Max(port[i],Math.Max(port[i+1],port[i+2]))>3)lit++;
}
var mean=difference/(double)(width*height);
reports.Add(new{key,meanDifference=mean,divergentPixels=divergent,litPixels=lit,renderer.SubmittedCurrentTradeRoutes,renderer.SubmittedCurrentTradeHotspots,
controlledNormalScale=2,controlledNormalOffset=-1,controlledSharedMultiplier=1,originalBlend="SourceAlpha/One/Add",fullParity=false});
if(mean>.5||divergent>width*height*.01||lit<20)
{
File.WriteAllText(Path.Combine(output,"report.json"),JsonSerializer.Serialize(reports,new JsonSerializerOptions{WriteIndented=true}));
throw new InvalidOperationException($"Trade shader diverged from original DXBC: {key},mean{mean},pixels{divergent},lit{lit}.");
}
}
}
renderer.SetCurrentTradeFlows([]);renderer.Prepare(GalaxyCamera.Create(new(0,0,0),width,height,0,0,20));
if(renderer.SubmittedCurrentTradeRoutes!=0||renderer.SubmittedCurrentTradeHotspots!=0||renderer.HasOriginalCurrentTradeAnimation)throw new InvalidOperationException("Disabled current route buffer remained active.");
reports.Add(new{disabledClear=true,gameProcessAccess=false,syntheticCoordinatesAndFlows=true});
File.WriteAllText(Path.Combine(output,"report.json"),JsonSerializer.Serialize(reports,new JsonSerializerOptions{WriteIndented=true}));Console.WriteLine(JsonSerializer.Serialize(reports));
byte[] Read()
{
var desc=texture.Description;desc.BindFlags=BindFlags.None;desc.CPUAccessFlags=CpuAccessFlags.Read;desc.Usage=ResourceUsage.Staging;
using var staging=device.CreateTexture2D(desc);context.CopyResource(staging,texture);var mapped=context.Map(staging,0,MapMode.Read);var bytes=new byte[width*height*4];
for(var y=0;y<height;y++)Marshal.Copy(mapped.DataPointer+(int)(y*mapped.RowPitch),bytes,y*width*4,width*4);context.Unmap(staging,0);return bytes;
}
namespace XFEEDToolBox.Rendering
{
internal sealed partial class GalaxyGraphics:IDisposable
{
private readonly ID3D11Device device;private readonly ID3D11DeviceContext context;private readonly ID3D11SamplerState sampler;private readonly ID3D11BlendState additive;
internal int DrawCalls;
private readonly ID3D11VertexShader originalRouteVs,originalHotspotVs;
private readonly ID3D11PixelShader originalRoutePs,originalHotspotPs;
private readonly ID3D11Buffer originalB0,originalB1,originalRouteB2,originalHotspotB2;
internal GalaxyGraphics(ID3D11Device d,ID3D11DeviceContext c)
{
device=d;context=c;sampler=d.CreateSamplerState(new SamplerDescription(Filter.MinMagMipLinear,TextureAddressMode.Wrap,TextureAddressMode.Wrap,TextureAddressMode.Wrap));additive=d.CreateBlendState(new BlendDescription(Blend.One,Blend.InverseSourceAlpha));
InitializeOriginalCurrentTrade(Path.GetFullPath("XFEEDToolBox/Assets/GalaxyMap.hlsl"));
var folder="artifacts/galaxy-query-research/current-trade/effects/";
originalRouteVs=d.CreateVertexShader(File.ReadAllBytes(folder+"vs_RouteInstances_Forward0_0_Win64_SM50.dxbc"));originalRoutePs=d.CreatePixelShader(File.ReadAllBytes(folder+"ps_RouteInstances_Forward0_0_Win64_SM50.dxbc"));
originalHotspotVs=d.CreateVertexShader(File.ReadAllBytes(folder+"vs_RouteHotspotInstances_Forward0_0_Win64_SM50.dxbc"));originalHotspotPs=d.CreatePixelShader(File.ReadAllBytes(folder+"ps_RouteHotspotInstances_Forward0_0_Win64_SM50.dxbc"));
originalB0=d.CreateBuffer(128,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);originalB1=d.CreateBuffer(280*16,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
originalRouteB2=d.CreateBuffer(96,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);originalHotspotB2=d.CreateBuffer(80,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
}
internal void Prepare(GalaxyCamera camera)
{
currentTradeTimers0=new(.21f,.37f,.53f,.79f);currentTradeTimers1=new(.31f,.47f,.63f,.83f);currentTradeLastFrame=0;
DrawOriginalCurrentTrade(camera);
}
internal void DrawPort(bool hotspot)
{
context.VSSetConstantBuffer(5,currentTradeConstants);context.PSSetConstantBuffer(5,currentTradeConstants);
context.IASetInputLayout(currentTradeLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.OMSetBlendState(currentTradeBlend);
context.IASetVertexBuffer(0,hotspot?currentTradeHotspotMesh:currentTradeRouteMesh,8);context.IASetVertexBuffer(1,hotspot?currentTradeHotspotBuffer!:currentTradeRouteBuffer!,44);
context.IASetIndexBuffer(hotspot?currentTradeHotspotIndices:currentTradeRouteIndices,Format.R16_UInt,0);
context.VSSetShader(hotspot?currentTradeHotspotVs:currentTradeRouteVs);context.PSSetShader(hotspot?currentTradeHotspotPs:currentTradeRoutePs);
context.PSSetShaderResource(13,currentTradeRouteView);context.PSSetShaderResource(14,currentTradeTrailView);context.PSSetShaderResource(15,currentTradeHotspotView);context.PSSetSampler(0,currentTradeSampler);
context.DrawIndexedInstanced(hotspot?6u:192u,(uint)(hotspot?SubmittedCurrentTradeHotspots:SubmittedCurrentTradeRoutes),0,0,0);
}
internal void DrawOriginal(GalaxyCamera camera,bool hotspot)
{
var eye=new Vector3((float)(camera.Eye.X+49985),(float)(camera.Eye.Y+40985),(float)(camera.Eye.Z+24105));
var right=new Vector3((float)camera.Right.X,(float)camera.Right.Y,(float)camera.Right.Z);var up=new Vector3((float)camera.Up.X,(float)camera.Up.Y,(float)camera.Up.Z);var forward=new Vector3((float)camera.Forward.X,(float)camera.Forward.Y,(float)camera.Forward.Z);
var x=right*(float)(camera.FocalLength*2/camera.Width);var y=up*(float)(camera.FocalLength*2/camera.Height);
Vector4[] projection=[new(0,0,0,2),new(0,0,0,-1),default,default,new(x,-Vector3.Dot(x,eye)),new(y,-Vector3.Dot(y,eye)),new(0,0,0,(float)camera.Near),new(forward,-Vector3.Dot(forward,eye))];
var globals=new Vector4[280];globals[90]=new(0,1,0,0);globals[277]=new(right,0);globals[278]=new(up,0);globals[279]=new(forward,0);
Upload<Vector4>(originalB0,projection);Upload<Vector4>(originalB1,globals);
if(hotspot)Upload<Vector4>(originalHotspotB2,[new(2,1,1,.25f),new(.75f,3,2,1),currentTradeTimers0,currentTradeTimers1,new(.65f,0,0,0)]);
else Upload<Vector4>(originalRouteB2,[new(5,1,1,.25f),new(.75f,3,2,1),new(4,0,0,0),currentTradeTimers0,currentTradeTimers1,new(1,0,0,0)]);
context.VSSetConstantBuffer(0,originalB0);context.VSSetConstantBuffer(1,originalB1);context.PSSetConstantBuffer(1,originalB1);context.VSSetConstantBuffer(2,hotspot?originalHotspotB2:originalRouteB2);context.PSSetConstantBuffer(2,hotspot?originalHotspotB2:originalRouteB2);
context.PSSetShaderResource(0,hotspot?currentTradeHotspotView:currentTradeRouteView);context.PSSetShaderResource(1,currentTradeTrailView);
context.VSSetShader(hotspot?originalHotspotVs:originalRouteVs);context.PSSetShader(hotspot?originalHotspotPs:originalRoutePs);
context.DrawIndexedInstanced(hotspot?6u:192u,(uint)(hotspot?SubmittedCurrentTradeHotspots:SubmittedCurrentTradeRoutes),0,0,0);
}
private unsafe void Upload<T>(ID3D11Buffer buffer,ReadOnlySpan<T> data) where T:unmanaged
{var mapped=context.Map(buffer,0,MapMode.WriteDiscard);try{MemoryMarshal.AsBytes(data).CopyTo(new Span<byte>((void*)mapped.DataPointer,data.Length*Marshal.SizeOf<T>()));}finally{context.Unmap(buffer,0);}}
public void Dispose(){DisposeOriginalCurrentTrade();sampler.Dispose();additive.Dispose();originalRouteVs.Dispose();originalRoutePs.Dispose();originalHotspotVs.Dispose();originalHotspotPs.Dispose();originalB0.Dispose();originalB1.Dispose();originalRouteB2.Dispose();originalHotspotB2.Dispose();}
}
}
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text.Json;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using Vortice.Mathematics;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Rendering;
using static Vortice.Direct3D11.D3D11;
const int width=1600,height=900;
D3D11CreateDevice(IntPtr.Zero,DriverType.Warp,DeviceCreationFlags.BgraSupport,[FeatureLevel.Level_11_0],out var device,out _,out var context).CheckError();
using var d=device;using var c=context;using var renderer=new GalaxyGraphics(device,context);
using var texture=device.CreateTexture2D(Format.B8G8R8A8_UNorm,width,height,mipLevels:1,bindFlags:BindFlags.RenderTarget);
using var target=device.CreateRenderTargetView(texture);
using var rasterizer=device.CreateRasterizerState(new RasterizerDescription(CullMode.None,FillMode.Solid));
using var noDepth=device.CreateDepthStencilState(new DepthStencilDescription(false,DepthWriteMask.Zero,ComparisonFunction.Always));
context.OMSetRenderTargets(target);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);context.OMSetDepthStencilState(noDepth);
var output=Path.GetFullPath("artifacts/galaxy-trade-shader-validation");Directory.CreateDirectory(output);var reports=new List<object>();
var flows=Enumerable.Range(0,13).Select(i=>
{
var from=new GalacticSystem("Synthetic from",new(-8,(i-6)*1.5,0),9007199254740993+i,"G");
var to=new GalacticSystem("Synthetic to",new(8,(i-6)*1.5,2),9007199254741993+i,"G");
return new GalaxyCurrentTradeRenderFlow(new(from.SystemAddress!.Value,to.SystemAddress!.Value,i,.025f+i*.05f,[]),from,to);
}).ToArray();
renderer.SetCurrentTradeFlows(flows);
foreach(var (name,yaw,pitch,scale) in new[]{("front",0d,0d,20d),("tilted",.3,.4,16d),("oblique",-.5,-.3,25d)})
{
var camera=GalaxyCamera.Create(new(0,0,0),width,height,yaw,pitch,scale);
renderer.Prepare(camera);
foreach(var hotspot in new[]{false,true})
{
context.ClearRenderTargetView(target,new Color4(0,0,0,1));renderer.DrawPort(hotspot);var port=Read();
context.ClearRenderTargetView(target,new Color4(0,0,0,1));renderer.DrawOriginal(camera,hotspot);var original=Read();
var key=name+(hotspot?"-hotspots":"-routes");File.WriteAllBytes(Path.Combine(output,key+".bgra"),port);File.WriteAllBytes(Path.Combine(output,key+"-original.bgra"),original);
long difference=0;var divergent=0;var lit=0;
for(var i=0;i<port.Length;i+=4)
{
var delta=0;for(var channel=0;channel<3;channel++)delta=Math.Max(delta,Math.Abs(port[i+channel]-original[i+channel]));
difference+=delta;if(delta>3)divergent++;
if(Math.Max(port[i],Math.Max(port[i+1],port[i+2]))>3)lit++;
}
var mean=difference/(double)(width*height);
reports.Add(new{key,meanDifference=mean,divergentPixels=divergent,litPixels=lit,renderer.SubmittedCurrentTradeRoutes,renderer.SubmittedCurrentTradeHotspots,
controlledNormalScale=2,controlledNormalOffset=-1,controlledSharedMultiplier=1,originalBlend="SourceAlpha/One/Add",fullParity=false});
if(mean>.5||divergent>width*height*.01||lit<20)
{
File.WriteAllText(Path.Combine(output,"report.json"),JsonSerializer.Serialize(reports,new JsonSerializerOptions{WriteIndented=true}));
throw new InvalidOperationException($"Trade shader diverged from original DXBC: {key},mean{mean},pixels{divergent},lit{lit}.");
}
}
}
renderer.SetCurrentTradeFlows([]);renderer.Prepare(GalaxyCamera.Create(new(0,0,0),width,height,0,0,20));
if(renderer.SubmittedCurrentTradeRoutes!=0||renderer.SubmittedCurrentTradeHotspots!=0||renderer.HasOriginalCurrentTradeAnimation)throw new InvalidOperationException("Disabled current route buffer remained active.");
reports.Add(new{disabledClear=true,gameProcessAccess=false,syntheticCoordinatesAndFlows=true});
File.WriteAllText(Path.Combine(output,"report.json"),JsonSerializer.Serialize(reports,new JsonSerializerOptions{WriteIndented=true}));Console.WriteLine(JsonSerializer.Serialize(reports));
byte[] Read()
{
var desc=texture.Description;desc.BindFlags=BindFlags.None;desc.CPUAccessFlags=CpuAccessFlags.Read;desc.Usage=ResourceUsage.Staging;
using var staging=device.CreateTexture2D(desc);context.CopyResource(staging,texture);var mapped=context.Map(staging,0,MapMode.Read);var bytes=new byte[width*height*4];
for(var y=0;y<height;y++)Marshal.Copy(mapped.DataPointer+(int)(y*mapped.RowPitch),bytes,y*width*4,width*4);context.Unmap(staging,0);return bytes;
}
namespace XFEEDToolBox.Rendering
{
internal sealed partial class GalaxyGraphics:IDisposable
{
private readonly ID3D11Device device;private readonly ID3D11DeviceContext context;private readonly ID3D11SamplerState sampler;private readonly ID3D11BlendState additive;
internal int DrawCalls;
private readonly ID3D11VertexShader originalRouteVs,originalHotspotVs;
private readonly ID3D11PixelShader originalRoutePs,originalHotspotPs;
private readonly ID3D11Buffer originalB0,originalB1,originalRouteB2,originalHotspotB2;
internal GalaxyGraphics(ID3D11Device d,ID3D11DeviceContext c)
{
device=d;context=c;sampler=d.CreateSamplerState(new SamplerDescription(Filter.MinMagMipLinear,TextureAddressMode.Wrap,TextureAddressMode.Wrap,TextureAddressMode.Wrap));additive=d.CreateBlendState(new BlendDescription(Blend.One,Blend.InverseSourceAlpha));
InitializeOriginalCurrentTrade(Path.GetFullPath("XFEEDToolBox/Assets/GalaxyMap.hlsl"));
var folder="artifacts/galaxy-query-research/current-trade/effects/";
originalRouteVs=d.CreateVertexShader(File.ReadAllBytes(folder+"vs_RouteInstances_Forward0_0_Win64_SM50.dxbc"));originalRoutePs=d.CreatePixelShader(File.ReadAllBytes(folder+"ps_RouteInstances_Forward0_0_Win64_SM50.dxbc"));
originalHotspotVs=d.CreateVertexShader(File.ReadAllBytes(folder+"vs_RouteHotspotInstances_Forward0_0_Win64_SM50.dxbc"));originalHotspotPs=d.CreatePixelShader(File.ReadAllBytes(folder+"ps_RouteHotspotInstances_Forward0_0_Win64_SM50.dxbc"));
originalB0=d.CreateBuffer(128,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);originalB1=d.CreateBuffer(280*16,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
originalRouteB2=d.CreateBuffer(96,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);originalHotspotB2=d.CreateBuffer(80,BindFlags.ConstantBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
}
internal void Prepare(GalaxyCamera camera)
{
currentTradeTimers0=new(.21f,.37f,.53f,.79f);currentTradeTimers1=new(.31f,.47f,.63f,.83f);currentTradeLastFrame=0;
DrawOriginalCurrentTrade(camera);
}
internal void DrawPort(bool hotspot)
{
context.VSSetConstantBuffer(5,currentTradeConstants);context.PSSetConstantBuffer(5,currentTradeConstants);
context.IASetInputLayout(currentTradeLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.OMSetBlendState(currentTradeBlend);
context.IASetVertexBuffer(0,hotspot?currentTradeHotspotMesh:currentTradeRouteMesh,8);context.IASetVertexBuffer(1,hotspot?currentTradeHotspotBuffer!:currentTradeRouteBuffer!,44);
context.IASetIndexBuffer(hotspot?currentTradeHotspotIndices:currentTradeRouteIndices,Format.R16_UInt,0);
context.VSSetShader(hotspot?currentTradeHotspotVs:currentTradeRouteVs);context.PSSetShader(hotspot?currentTradeHotspotPs:currentTradeRoutePs);
context.PSSetShaderResource(13,currentTradeRouteView);context.PSSetShaderResource(14,currentTradeTrailView);context.PSSetShaderResource(15,currentTradeHotspotView);context.PSSetSampler(0,currentTradeSampler);
context.DrawIndexedInstanced(hotspot?6u:192u,(uint)(hotspot?SubmittedCurrentTradeHotspots:SubmittedCurrentTradeRoutes),0,0,0);
}
internal void DrawOriginal(GalaxyCamera camera,bool hotspot)
{
var eye=new Vector3((float)(camera.Eye.X+49985),(float)(camera.Eye.Y+40985),(float)(camera.Eye.Z+24105));
var right=new Vector3((float)camera.Right.X,(float)camera.Right.Y,(float)camera.Right.Z);var up=new Vector3((float)camera.Up.X,(float)camera.Up.Y,(float)camera.Up.Z);var forward=new Vector3((float)camera.Forward.X,(float)camera.Forward.Y,(float)camera.Forward.Z);
var x=right*(float)(camera.FocalLength*2/camera.Width);var y=up*(float)(camera.FocalLength*2/camera.Height);
Vector4[] projection=[new(0,0,0,2),new(0,0,0,-1),default,default,new(x,-Vector3.Dot(x,eye)),new(y,-Vector3.Dot(y,eye)),new(0,0,0,(float)camera.Near),new(forward,-Vector3.Dot(forward,eye))];
var globals=new Vector4[280];globals[90]=new(0,1,0,0);globals[277]=new(right,0);globals[278]=new(up,0);globals[279]=new(forward,0);
Upload<Vector4>(originalB0,projection);Upload<Vector4>(originalB1,globals);
if(hotspot)Upload<Vector4>(originalHotspotB2,[new(2,1,1,.25f),new(.75f,3,2,1),currentTradeTimers0,currentTradeTimers1,new(.65f,0,0,0)]);
else Upload<Vector4>(originalRouteB2,[new(5,1,1,.25f),new(.75f,3,2,1),new(4,0,0,0),currentTradeTimers0,currentTradeTimers1,new(1,0,0,0)]);
context.VSSetConstantBuffer(0,originalB0);context.VSSetConstantBuffer(1,originalB1);context.PSSetConstantBuffer(1,originalB1);context.VSSetConstantBuffer(2,hotspot?originalHotspotB2:originalRouteB2);context.PSSetConstantBuffer(2,hotspot?originalHotspotB2:originalRouteB2);
context.PSSetShaderResource(0,hotspot?currentTradeHotspotView:currentTradeRouteView);context.PSSetShaderResource(1,currentTradeTrailView);
context.VSSetShader(hotspot?originalHotspotVs:originalRouteVs);context.PSSetShader(hotspot?originalHotspotPs:originalRoutePs);
context.DrawIndexedInstanced(hotspot?6u:192u,(uint)(hotspot?SubmittedCurrentTradeHotspots:SubmittedCurrentTradeRoutes),0,0,0);
}
private unsafe void Upload<T>(ID3D11Buffer buffer,ReadOnlySpan<T> data) where T:unmanaged
{var mapped=context.Map(buffer,0,MapMode.WriteDiscard);try{MemoryMarshal.AsBytes(data).CopyTo(new Span<byte>((void*)mapped.DataPointer,data.Length*Marshal.SizeOf<T>()));}finally{context.Unmap(buffer,0);}}
public void Dispose(){DisposeOriginalCurrentTrade();sampler.Dispose();additive.Dispose();originalRouteVs.Dispose();originalRoutePs.Dispose();originalHotspotVs.Dispose();originalHotspotPs.Dispose();originalB0.Dispose();originalB1.Dispose();originalRouteB2.Dispose();originalHotspotB2.Dispose();}
}
}