using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; namespace Auspex.Rendering.Direct3D; internal sealed class Direct3DRenderer : IDisposable { public const int MAX_FANS = 2048; public const int MAX_TRIS = 65536; public const int MAX_STROKE_SEGMENTS = 245760; public const int MAX_CLIP_ZONES = 3072; public const int MAX_TEXTURED_QUADS = 256; private unsafe ID3D11DepthStencilState* _clipZoneDSS; private unsafe ID3D11DepthStencilState* _shapeDSS; private readonly TriangleFillShader.Data _triFillDynamicData; private TriangleFillShader.Data.Builder? _triFillDynamicBuilder; private readonly FanFillShader.Data _fanFillDynamicData; private FanFillShader.Data.Builder? _fanFillDynamicBuilder; private readonly StrokeShader.Data _strokeDynamicData; private StrokeShader.Data.Builder? _strokeDynamicBuilder; private readonly QuadBlitter.Data _texturedQuadDynamicData; private QuadBlitter.Data.Builder? _texturedQuadDynamicBuilder; internal readonly List SharedPath = new List(); private unsafe ID3D11RasterizerState* _rasterizerState; public FrameRenderContext FrameRenderContext { get; init; } = new FrameRenderContext(); internal FrameRenderTarget? FrameRenderTarget { get; private set; } public TriangleFillShader TriangleFillShader { get; init; } public FanFillShader FanFillShader { get; init; } public StrokeShader StrokeShader { get; init; } public QuadBlitter QuadBlitter { get; init; } public FullscreenPassShader? FSP { get; init; } public ClipZoneShader ClipZone { get; init; } internal SceneDepthCapture SceneDepth { get; init; } public Matrix4x4 ViewProj { get; private set; } public Vector2 ViewportSize { get; private set; } public Vector3 CameraRight { get; private set; } public Vector3 CameraUp { get; private set; } public bool FanDegraded { get; private set; } public bool StrokeDegraded { get; private set; } public bool FSPDegraded { get; private set; } public bool TexturedQuadDegraded { get; private set; } internal HighResFontAtlas? WorldFont { get; private set; } public Direct3DRenderer() { try { FanFillShader = new FanFillShader(FrameRenderContext); } catch (Exception exception) { AuspexService.Log.Error(exception, "[Auspex] Failed to compile fan shader; starting in degraded mode."); FanDegraded = true; } _fanFillDynamicData = new FanFillShader.Data(FrameRenderContext, FanDegraded ? 1 : 2048, dynamic: true); try { StrokeShader = new StrokeShader(FrameRenderContext); } catch (Exception exception2) { AuspexService.Log.Error(exception2, "[Auspex] Failed to compile stroke shader; starting in degraded mode."); StrokeDegraded = true; } _strokeDynamicData = new StrokeShader.Data(FrameRenderContext, StrokeDegraded ? 1 : 245760, dynamic: true); try { QuadBlitter = new QuadBlitter(FrameRenderContext); } catch (Exception exception3) { AuspexService.Log.Error(exception3, "[Auspex] Failed to compile textured quad shader; starting in degraded mode."); TexturedQuadDegraded = true; } _texturedQuadDynamicData = new QuadBlitter.Data(FrameRenderContext, TexturedQuadDegraded ? 1 : 1536, dynamic: true); if (!TexturedQuadDegraded) { try { WorldFont = new HighResFontAtlas(FrameRenderContext); } catch (Exception exception4) { AuspexService.Log.Error(exception4, "[Auspex] Failed to create high-res world text font; world text will use low-res fallback."); } } try { TriangleFillShader = new TriangleFillShader(FrameRenderContext); _triFillDynamicData = new TriangleFillShader.Data(FrameRenderContext, 65536 + (FanDegraded ? 737280 : 0), dynamic: true); } catch { Dispose(); throw; } try { FSP = new FullscreenPassShader(FrameRenderContext); } catch (Exception exception5) { AuspexService.Log.Error(exception5, "[Auspex] Failed to compile FSP shader; starting in degraded mode."); FSPDegraded = true; } SceneDepth = new SceneDepthCapture(); ClipZone = new ClipZoneShader(FrameRenderContext, 3072); CreateRasterizerState(); CreateDepthStencilStates(); } private unsafe void CreateRasterizerState() { D3D11_RASTERIZER_DESC d3D11_RASTERIZER_DESC = new D3D11_RASTERIZER_DESC { FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID, CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE, DepthClipEnable = true, ScissorEnable = true }; ID3D11RasterizerState* rasterizerState = default(ID3D11RasterizerState*); Marshal.ThrowExceptionForHR(FrameRenderContext.Device->CreateRasterizerState(&d3D11_RASTERIZER_DESC, &rasterizerState)); _rasterizerState = rasterizerState; } private unsafe void CreateDepthStencilStates() { D3D11_DEPTH_STENCILOP_DESC d3D11_DEPTH_STENCILOP_DESC = new D3D11_DEPTH_STENCILOP_DESC { StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_REPLACE, StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS }; D3D11_DEPTH_STENCIL_DESC d3D11_DEPTH_STENCIL_DESC = new D3D11_DEPTH_STENCIL_DESC { DepthEnable = false, DepthWriteMask = D3D11_DEPTH_WRITE_MASK.D3D11_DEPTH_WRITE_MASK_ZERO, StencilEnable = true, StencilReadMask = byte.MaxValue, StencilWriteMask = byte.MaxValue, FrontFace = d3D11_DEPTH_STENCILOP_DESC, BackFace = d3D11_DEPTH_STENCILOP_DESC }; ID3D11DepthStencilState* ptr = default(ID3D11DepthStencilState*); Marshal.ThrowExceptionForHR(FrameRenderContext.Device->CreateDepthStencilState(&d3D11_DEPTH_STENCIL_DESC, &ptr)); _clipZoneDSS = ptr; D3D11_DEPTH_STENCILOP_DESC d3D11_DEPTH_STENCILOP_DESC2 = new D3D11_DEPTH_STENCILOP_DESC { StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP, StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_EQUAL }; D3D11_DEPTH_STENCIL_DESC d3D11_DEPTH_STENCIL_DESC2 = new D3D11_DEPTH_STENCIL_DESC { DepthEnable = false, DepthWriteMask = D3D11_DEPTH_WRITE_MASK.D3D11_DEPTH_WRITE_MASK_ZERO, StencilEnable = true, StencilReadMask = byte.MaxValue, StencilWriteMask = 0, FrontFace = d3D11_DEPTH_STENCILOP_DESC2, BackFace = d3D11_DEPTH_STENCILOP_DESC2 }; Marshal.ThrowExceptionForHR(FrameRenderContext.Device->CreateDepthStencilState(&d3D11_DEPTH_STENCIL_DESC2, &ptr)); _shapeDSS = ptr; } public unsafe void Dispose() { WorldFont?.Dispose(); FrameRenderTarget?.Dispose(); _triFillDynamicBuilder?.Dispose(); _triFillDynamicData?.Dispose(); _fanFillDynamicBuilder?.Dispose(); _fanFillDynamicData?.Dispose(); _strokeDynamicBuilder?.Dispose(); _strokeDynamicData?.Dispose(); _texturedQuadDynamicBuilder?.Dispose(); _texturedQuadDynamicData?.Dispose(); if (!FanDegraded) { FanFillShader.Dispose(); } if (!StrokeDegraded) { StrokeShader.Dispose(); } if (!TexturedQuadDegraded) { QuadBlitter.Dispose(); } TriangleFillShader?.Dispose(); FSP?.Dispose(); SceneDepth.Dispose(); ClipZone.Dispose(); if (_clipZoneDSS != null) { _clipZoneDSS->Release(); _clipZoneDSS = null; } if (_shapeDSS != null) { _shapeDSS->Release(); _shapeDSS = null; } if (_rasterizerState != null) { _rasterizerState->Release(); _rasterizerState = null; } FrameRenderContext.Dispose(); GC.SuppressFinalize(this); } internal unsafe void BeginFrame() { Device* ptr = Device.Instance(); ViewportSize = new Vector2(ptr->Width, ptr->Height); Control* ptr2 = Control.Instance(); ViewProj = *(Matrix4x4*)(&ptr2->ViewProjectionMatrix); SceneDepth.Update(); FFXIVClientStructs.FFXIV.Client.Game.Camera* activeCamera = ptr2->CameraManager.GetActiveCamera(); FFXIVClientStructs.FFXIV.Client.Graphics.Render.Camera* ptr3 = ((activeCamera != null) ? activeCamera->SceneCamera.RenderCamera : null); if (ptr3 != null && Matrix4x4.Invert(ptr3->ProjectionMatrix, out var result)) { Matrix4x4 matrix4x = ViewProj * result; CameraRight = Vector3.Normalize(new Vector3(matrix4x.M11, matrix4x.M21, matrix4x.M31)); CameraUp = Vector3.Normalize(new Vector3(matrix4x.M12, matrix4x.M22, matrix4x.M32)); } else { CameraRight = Vector3.UnitX; CameraUp = Vector3.UnitY; } Vector2 pixelToUv = ((SceneDepth.SRV != null) ? (SceneDepth.UvScale / ViewportSize) : Vector2.Zero); TriangleFillShader.UpdateConstants(FrameRenderContext, new TriangleFillShader.Constants { ViewProj = ViewProj, PixelToUv = pixelToUv }); if (!FanDegraded) { FanFillShader.UpdateConstants(FrameRenderContext, new FanFillShader.Constants { ViewProj = ViewProj, PixelToUv = pixelToUv }); } if (!StrokeDegraded) { StrokeShader.UpdateConstants(FrameRenderContext, new StrokeShader.Constants { ViewProj = ViewProj, RenderTargetSize = new Vector2(ViewportSize.X, ViewportSize.Y), PixelToUv = pixelToUv }); } if (!TexturedQuadDegraded) { QuadBlitter.UpdateConstants(FrameRenderContext, new QuadBlitter.Constants { ViewProj = ViewProj }); } if (!FSPDegraded) { bool flag = AuspexService.Hints.ClipNativeUI && !SceneDepth.IsResolutionScaled; FSP.UpdateConstants(FrameRenderContext, new FullscreenPassShader.Constants { MaxAlpha = AuspexService.Hints.MaxAlphaFraction, ClipNativeUI = (flag ? 1f : 0f) }); } if (FrameRenderTarget == null || FrameRenderTarget.Size != ViewportSize) { FrameRenderTarget?.Dispose(); FrameRenderTarget = new FrameRenderTarget(FrameRenderContext, (int)ViewportSize.X, (int)ViewportSize.Y, AuspexService.Hints.AlphaBlendMode); } FrameRenderTarget.Bind(FrameRenderContext); FrameRenderContext.Context->RSSetState(_rasterizerState); RECT rECT = new RECT { left = 0, top = 0, right = (int)ViewportSize.X, bottom = (int)ViewportSize.Y }; FrameRenderContext.Context->RSSetScissorRects(1u, &rECT); } internal void Flush() { if (_triFillDynamicBuilder != null) { _triFillDynamicBuilder.Dispose(); _triFillDynamicBuilder = null; TriangleFillShader.Bind(FrameRenderContext); _triFillDynamicData.DrawAll(FrameRenderContext); } if (!FanDegraded && _fanFillDynamicBuilder != null) { _fanFillDynamicBuilder.Dispose(); _fanFillDynamicBuilder = null; FanFillShader.Bind(FrameRenderContext); _fanFillDynamicData.DrawAll(FrameRenderContext); } if (!StrokeDegraded && _strokeDynamicBuilder != null) { _strokeDynamicBuilder.Dispose(); _strokeDynamicBuilder = null; StrokeShader.Bind(FrameRenderContext); _strokeDynamicData.DrawAll(FrameRenderContext); } if (!TexturedQuadDegraded && _texturedQuadDynamicBuilder != null) { _texturedQuadDynamicBuilder.Dispose(); _texturedQuadDynamicBuilder = null; QuadBlitter.Draw(FrameRenderContext, _texturedQuadDynamicData); } } internal unsafe void SetScissorRect(Vector2 min, Vector2 max) { Flush(); RECT rECT = new RECT { left = (int)min.X, top = (int)min.Y, right = (int)max.X, bottom = (int)max.Y }; FrameRenderContext.Context->RSSetScissorRects(1u, &rECT); } internal unsafe void ClearScissorRect() { Flush(); RECT rECT = new RECT { left = 0, top = 0, right = (int)ViewportSize.X, bottom = (int)ViewportSize.Y }; FrameRenderContext.Context->RSSetScissorRects(1u, &rECT); } public void AddClipZone(Vector2 min, Vector2 max) { ClipZone.Add(min, max); } internal unsafe FrameRenderTarget EndFrame() { bool num = _triFillDynamicBuilder != null || (!FanDegraded && _fanFillDynamicBuilder != null) || (!StrokeDegraded && _strokeDynamicBuilder != null) || (!TexturedQuadDegraded && _texturedQuadDynamicBuilder != null); bool hasPending = ClipZone.HasPending; if (num && SceneDepth.SRV != null) { ID3D11DepthStencilView* clipStencilDSV = FrameRenderTarget.ClipStencilDSV; FrameRenderContext.Context->OMSetRenderTargets(0u, null, clipStencilDSV); FrameRenderContext.Context->ClearDepthStencilView(clipStencilDSV, 2u, 0f, 0); if (hasPending) { ClipZone.UpdateConstants(new ClipZoneShader.Constants { ViewportSize = new Vector2(ViewportSize.X, ViewportSize.Y) }); FrameRenderContext.Context->OMSetDepthStencilState(_clipZoneDSS, 1u); ClipZone.Flush(); } ID3D11RenderTargetView* baseRTV = FrameRenderTarget.BaseRTV; FrameRenderContext.Context->OMSetRenderTargets(1u, &baseRTV, clipStencilDSV); FrameRenderContext.Context->OMSetDepthStencilState(_shapeDSS, 0u); ID3D11ShaderResourceView* sRV = SceneDepth.SRV; FrameRenderContext.Context->PSSetShaderResources(0u, 1u, &sRV); } else { ClipZone.Discard(); } Flush(); if (SceneDepth.SRV != null) { ID3D11ShaderResourceView* ptr = null; FrameRenderContext.Context->PSSetShaderResources(0u, 1u, &ptr); } if (!FSPDegraded) { Device* ptr2 = Device.Instance(); if (ptr2 != null && ptr2->SwapChain != null && ptr2->SwapChain->BackBuffer != null && ptr2->SwapChain->BackBuffer->D3D11Texture2D != null) { ID3D11Texture2D* d3D11Texture2D = (ID3D11Texture2D*)ptr2->SwapChain->BackBuffer->D3D11Texture2D; FrameRenderTarget.ExecuteFSP(FrameRenderContext, d3D11Texture2D, FSP, AuspexService.Hints.ClipNativeUI && !SceneDepth.IsResolutionScaled); } else { AuspexService.Log.Warning("[Auspex] Direct3DRenderer.EndFrame: Device or BackBuffer is null; skipping combined pass."); } } else { FrameRenderTarget.CopyBaseToProcessed(FrameRenderContext); } FrameRenderContext.Execute(); return FrameRenderTarget; } public void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, uint colorA, uint colorB, uint colorC, AxDxParams p) { TriangleFillShader.Data.Builder triFills = GetTriFills(); triFills.Add(a, colorA.ToVector4(), p); triFills.Add(b, colorB.ToVector4(), p); triFills.Add(c, colorC.ToVector4(), p); } private void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Vector4 colorA, Vector4 colorB, Vector4 colorC, AxDxParams p) { TriangleFillShader.Data.Builder triFills = GetTriFills(); triFills.Add(a, colorA, p); triFills.Add(b, colorB, p); triFills.Add(c, colorC, p); } private TriangleFillShader.Data.Builder GetTriFills() { return _triFillDynamicBuilder ?? (_triFillDynamicBuilder = _triFillDynamicData.Map(FrameRenderContext)); } private void DrawTriangleFan(Vector3 center, float innerRadius, float outerRadius, float minAngle, float maxAngle, uint innerColor, uint outerColor, uint numSegments = 0u, AxDxParams p = default(AxDxParams)) { float num = maxAngle - minAngle; if (num == 0f) { return; } if (numSegments == 0) { numSegments = (uint)(MathF.Abs(num) * 8f); } float num2 = num / (float)numSegments; Vector4 vector = innerColor.ToVector4(); Vector4 vector2 = outerColor.ToVector4(); Vector3 vector3 = default(Vector3); for (int i = 0; i <= numSegments; i++) { float num3 = (float)Math.PI / 2f + minAngle + (float)i * num2; Vector3 vector4 = new Vector3(MathF.Cos(num3), 0f, MathF.Sin(num3)); if (i > 0) { if (innerRadius > 0f) { DrawTriangle(center + innerRadius * vector3, center + outerRadius * vector3, center + outerRadius * vector4, vector, vector2, vector2, p); DrawTriangle(center + outerRadius * vector4, center + innerRadius * vector4, center + innerRadius * vector3, vector2, vector, vector, p); } else { DrawTriangle(center, center + outerRadius * vector3, center + outerRadius * vector4, vector, vector2, vector2, p); } } vector3 = vector4; } } internal void DrawTriangleFanGradient(Vector3 center, float innerRadius, float outerRadius, float minAngle, float maxAngle, T gradient, uint numSegments = 0u, AxDxParams p = default(AxDxParams)) where T : IPctGradient { float num = maxAngle - minAngle; if (num == 0f) { return; } if (numSegments == 0) { numSegments = (uint)(MathF.Abs(num) * 8f); } float num2 = num / (float)numSegments; Vector3 vector = default(Vector3); for (int i = 0; i <= numSegments; i++) { float num3 = (float)Math.PI / 2f + minAngle + (float)i * num2; Vector3 vector2 = new Vector3(MathF.Cos(num3), 0f, MathF.Sin(num3)); if (i > 0) { if (innerRadius > 0f) { Vector3 vector3 = center + innerRadius * vector; Vector3 vector4 = center + outerRadius * vector; Vector3 vector5 = center + outerRadius * vector2; Vector3 vector6 = center + innerRadius * vector2; DrawTriangle(vector3, vector4, vector5, gradient.ColorAt(vector3).ToVector4(), gradient.ColorAt(vector4).ToVector4(), gradient.ColorAt(vector5).ToVector4(), p); DrawTriangle(vector5, vector6, vector3, gradient.ColorAt(vector5).ToVector4(), gradient.ColorAt(vector6).ToVector4(), gradient.ColorAt(vector3).ToVector4(), p); } else { Vector3 vector7 = center + outerRadius * vector; Vector3 vector8 = center + outerRadius * vector2; DrawTriangle(center, vector7, vector8, gradient.ColorAt(center).ToVector4(), gradient.ColorAt(vector7).ToVector4(), gradient.ColorAt(vector8).ToVector4(), p); } } vector = vector2; } } internal void DrawTerrainFan(Vector3 origin, float innerRadius, float outerRadius, float minAngle, float maxAngle, uint innerColor, uint outerColor, float resolution, float yOffset, uint numSegments = 0u, AxDxParams p = default(AxDxParams)) { float num = maxAngle - minAngle; if (num == 0f) { return; } uint num2 = ((numSegments != 0) ? numSegments : ((uint)MathF.Ceiling(MathF.Abs(num) * outerRadius / resolution))); if (num2 == 0) { num2 = 1u; } int num3 = Math.Max(1, (int)MathF.Ceiling((outerRadius - innerRadius) / resolution)); float num4 = num / (float)num2; Vector4 value = innerColor.ToVector4(); Vector4 value2 = outerColor.ToVector4(); Vector3 a = ((innerRadius == 0f) ? TerrainVertex(origin, Vector3.Zero, 0f, yOffset) : default(Vector3)); float num5 = (float)Math.PI / 2f + minAngle; Vector3 direction = new Vector3(MathF.Cos(num5), 0f, MathF.Sin(num5)); for (int i = 1; i <= num2; i++) { float num6 = (float)Math.PI / 2f + minAngle + (float)i * num4; Vector3 vector = new Vector3(MathF.Cos(num6), 0f, MathF.Sin(num6)); for (int j = 0; j < num3; j++) { float num7 = innerRadius + (outerRadius - innerRadius) * (float)j / (float)num3; float radius = innerRadius + (outerRadius - innerRadius) * (float)(j + 1) / (float)num3; float amount = (float)j / (float)num3; float amount2 = (float)(j + 1) / (float)num3; Vector4 vector2 = Vector4.Lerp(value, value2, amount); Vector4 vector3 = Vector4.Lerp(value, value2, amount2); if (num7 > 0f) { Vector3 vector4 = TerrainVertex(origin, direction, num7, yOffset); Vector3 b = TerrainVertex(origin, direction, radius, yOffset); Vector3 vector5 = TerrainVertex(origin, vector, radius, yOffset); Vector3 b2 = TerrainVertex(origin, vector, num7, yOffset); DrawTriangle(vector4, b, vector5, vector2, vector3, vector3, p); DrawTriangle(vector5, b2, vector4, vector3, vector2, vector2, p); } else { Vector3 b3 = TerrainVertex(origin, direction, radius, yOffset); Vector3 c = TerrainVertex(origin, vector, radius, yOffset); DrawTriangle(a, b3, c, vector2, vector3, vector3, p); } } direction = vector; } } internal void DrawTerrainFanGradient(Vector3 origin, float innerRadius, float outerRadius, float minAngle, float maxAngle, T gradient, float resolution, float yOffset, uint numSegments = 0u, AxDxParams p = default(AxDxParams)) where T : IPctGradient { float num = maxAngle - minAngle; if (num == 0f) { return; } uint num2 = ((numSegments != 0) ? numSegments : ((uint)MathF.Ceiling(MathF.Abs(num) * outerRadius / resolution))); if (num2 == 0) { num2 = 1u; } int num3 = Math.Max(1, (int)MathF.Ceiling((outerRadius - innerRadius) / resolution)); float num4 = num / (float)num2; Vector3 vector = ((innerRadius == 0f) ? TerrainVertex(origin, Vector3.Zero, 0f, yOffset) : default(Vector3)); Vector4 colorA = ((innerRadius == 0f) ? gradient.ColorAt(vector).ToVector4() : default(Vector4)); float num5 = (float)Math.PI / 2f + minAngle; Vector3 direction = new Vector3(MathF.Cos(num5), 0f, MathF.Sin(num5)); for (int i = 1; i <= num2; i++) { float num6 = (float)Math.PI / 2f + minAngle + (float)i * num4; Vector3 vector2 = new Vector3(MathF.Cos(num6), 0f, MathF.Sin(num6)); for (int j = 0; j < num3; j++) { float num7 = innerRadius + (outerRadius - innerRadius) * (float)j / (float)num3; float radius = innerRadius + (outerRadius - innerRadius) * (float)(j + 1) / (float)num3; if (num7 > 0f) { Vector3 vector3 = TerrainVertex(origin, direction, num7, yOffset); Vector3 vector4 = TerrainVertex(origin, direction, radius, yOffset); Vector3 vector5 = TerrainVertex(origin, vector2, radius, yOffset); Vector3 vector6 = TerrainVertex(origin, vector2, num7, yOffset); DrawTriangle(vector3, vector4, vector5, gradient.ColorAt(vector3).ToVector4(), gradient.ColorAt(vector4).ToVector4(), gradient.ColorAt(vector5).ToVector4(), p); DrawTriangle(vector5, vector6, vector3, gradient.ColorAt(vector5).ToVector4(), gradient.ColorAt(vector6).ToVector4(), gradient.ColorAt(vector3).ToVector4(), p); } else { Vector3 vector7 = TerrainVertex(origin, direction, radius, yOffset); Vector3 vector8 = TerrainVertex(origin, vector2, radius, yOffset); DrawTriangle(vector, vector7, vector8, colorA, gradient.ColorAt(vector7).ToVector4(), gradient.ColorAt(vector8).ToVector4(), p); } } direction = vector2; } } internal void DrawTerrainQuad(Vector3 a, Vector3 b, Vector3 c, Vector3 d, uint colorA, uint colorB, uint colorC, uint colorD, float resolution, float yOffset, AxDxParams p = default(AxDxParams)) { float num = MathF.Max(Vector3.Distance(a, b), Vector3.Distance(d, c)); float num2 = MathF.Max(Vector3.Distance(b, c), Vector3.Distance(a, d)); int num3 = Math.Max(1, (int)MathF.Ceiling(num / resolution)); int num4 = Math.Max(1, (int)MathF.Ceiling(num2 / resolution)); Vector4 a2 = colorA.ToVector4(); Vector4 b2 = colorB.ToVector4(); Vector4 c2 = colorC.ToVector4(); Vector4 d2 = colorD.ToVector4(); for (int i = 0; i < num3; i++) { float u = (float)i / (float)num3; float u2 = (float)(i + 1) / (float)num3; for (int j = 0; j < num4; j++) { float v = (float)j / (float)num4; float v2 = (float)(j + 1) / (float)num4; Vector3 vector = TerrainVertexBilinear(a, b, c, d, u, v, yOffset); Vector3 b3 = TerrainVertexBilinear(a, b, c, d, u2, v, yOffset); Vector3 vector2 = TerrainVertexBilinear(a, b, c, d, u2, v2, yOffset); Vector3 b4 = TerrainVertexBilinear(a, b, c, d, u, v2, yOffset); Vector4 vector3 = BilinearColorV4(a2, b2, c2, d2, u, v); Vector4 colorB2 = BilinearColorV4(a2, b2, c2, d2, u2, v); Vector4 vector4 = BilinearColorV4(a2, b2, c2, d2, u2, v2); Vector4 colorB3 = BilinearColorV4(a2, b2, c2, d2, u, v2); DrawTriangle(vector, b3, vector2, vector3, colorB2, vector4, p); DrawTriangle(vector2, b4, vector, vector4, colorB3, vector3, p); } } } internal void DrawTerrainQuadGradient(Vector3 a, Vector3 b, Vector3 c, Vector3 d, T gradient, float resolution, float yOffset, AxDxParams p = default(AxDxParams)) where T : IPctGradient { float num = MathF.Max(Vector3.Distance(a, b), Vector3.Distance(d, c)); float num2 = MathF.Max(Vector3.Distance(b, c), Vector3.Distance(a, d)); int num3 = Math.Max(1, (int)MathF.Ceiling(num / resolution)); int num4 = Math.Max(1, (int)MathF.Ceiling(num2 / resolution)); for (int i = 0; i < num3; i++) { float u = (float)i / (float)num3; float u2 = (float)(i + 1) / (float)num3; for (int j = 0; j < num4; j++) { float v = (float)j / (float)num4; float v2 = (float)(j + 1) / (float)num4; Vector3 vector = TerrainVertexBilinear(a, b, c, d, u, v, yOffset); Vector3 vector2 = TerrainVertexBilinear(a, b, c, d, u2, v, yOffset); Vector3 vector3 = TerrainVertexBilinear(a, b, c, d, u2, v2, yOffset); Vector3 vector4 = TerrainVertexBilinear(a, b, c, d, u, v2, yOffset); DrawTriangle(vector, vector2, vector3, gradient.ColorAt(vector).ToVector4(), gradient.ColorAt(vector2).ToVector4(), gradient.ColorAt(vector3).ToVector4(), p); DrawTriangle(vector3, vector4, vector, gradient.ColorAt(vector3).ToVector4(), gradient.ColorAt(vector4).ToVector4(), gradient.ColorAt(vector).ToVector4(), p); } } } private static Vector3 TerrainVertex(Vector3 origin, Vector3 direction, float radius, float yOffset) { float x = origin.X + direction.X * radius; float z = origin.Z + direction.Z * radius; float terrainYRaw = AxTerrainQuery.GetTerrainYRaw(x, z); float y = (float.IsNaN(terrainYRaw) ? origin.Y : terrainYRaw) + yOffset; return new Vector3(x, y, z); } private static Vector3 TerrainVertexBilinear(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float u, float v, float yOffset) { Vector3 result = a * (1f - u) * (1f - v) + b * u * (1f - v) + c * u * v + d * (1f - u) * v; float terrainYRaw = AxTerrainQuery.GetTerrainYRaw(result.X, result.Z); float y = result.Y; result.Y = (float.IsNaN(terrainYRaw) ? y : terrainYRaw) + yOffset; return result; } private static Vector4 BilinearColorV4(Vector4 a, Vector4 b, Vector4 c, Vector4 d, float u, float v) { return a * (1f - u) * (1f - v) + b * u * (1f - v) + c * u * v + d * (1f - u) * v; } public void DrawFan(Vector3 center, float innerRadius, float outerRadius, float minAngle, float maxAngle, uint innerColor, uint outerColor, uint numSegments = 0u, AxDxParams p = default(AxDxParams)) { if (!FanDegraded) { if (numSegments == 0) { numSegments = (uint)MathF.Max(4f, MathF.Ceiling(MathF.Abs(maxAngle - minAngle) * 16f)); } numSegments = Math.Min(numSegments, 360u); GetFanFills().Add(center, innerRadius, outerRadius, minAngle, maxAngle, innerColor.ToVector4(), outerColor.ToVector4(), numSegments, p); } else { DrawTriangleFan(center, innerRadius, outerRadius, minAngle, maxAngle, innerColor, outerColor, numSegments, p); } } private FanFillShader.Data.Builder GetFanFills() { return _fanFillDynamicBuilder ?? (_fanFillDynamicBuilder = _fanFillDynamicData.Map(FrameRenderContext)); } public void DrawStroke(List world, float thickness, uint color, bool closed = false, float dashLength = 0f, float gapLength = 0f, float dashOffset = 0f, uint? colorEnd = null, AxLineCap cap = AxLineCap.Butt, AxLineJoin join = AxLineJoin.None, AxDxParams p = default(AxDxParams)) { if (!StrokeDegraded) { GetStroke().Add(world, thickness, color.ToVector4(), closed, dashLength, gapLength, dashOffset, colorEnd.HasValue ? new Vector4?(colorEnd.Value.ToVector4()) : ((Vector4?)null), (float)cap, (float)join, p); } } public void DrawStroke(List world, ReadOnlySpan thicknesses, uint color, bool closed = false, float dashLength = 0f, float gapLength = 0f, float dashOffset = 0f, uint? colorEnd = null, AxLineCap cap = AxLineCap.Butt, AxLineJoin join = AxLineJoin.None, AxDxParams p = default(AxDxParams)) { if (!StrokeDegraded) { GetStroke().Add(world, thicknesses, color.ToVector4(), closed, dashLength, gapLength, dashOffset, colorEnd.HasValue ? new Vector4?(colorEnd.Value.ToVector4()) : ((Vector4?)null), (float)cap, (float)join, p); } } private StrokeShader.Data.Builder GetStroke() { return _strokeDynamicBuilder ?? (_strokeDynamicBuilder = _strokeDynamicData.Map(FrameRenderContext)); } public void DrawTexturedQuad(IntPtr textureSRV, Vector3 a, Vector3 b, Vector3 c, Vector3 d, uint color) { DrawTexturedQuad(textureSRV, a, b, c, d, color, new Vector2(0f, 0f), new Vector2(1f, 1f)); } public void DrawTexturedQuad(IntPtr textureSRV, Vector3 a, Vector3 b, Vector3 c, Vector3 d, uint color, Vector2 uvMin, Vector2 uvMax) { QuadBlitter.Data.Builder texturedQuads = GetTexturedQuads(); texturedQuads.SetTexture(textureSRV); Vector4 color2 = color.ToVector4(); texturedQuads.Add(a, new Vector2(uvMin.X, uvMin.Y), color2); texturedQuads.Add(b, new Vector2(uvMax.X, uvMin.Y), color2); texturedQuads.Add(c, new Vector2(uvMax.X, uvMax.Y), color2); texturedQuads.Add(a, new Vector2(uvMin.X, uvMin.Y), color2); texturedQuads.Add(c, new Vector2(uvMax.X, uvMax.Y), color2); texturedQuads.Add(d, new Vector2(uvMin.X, uvMax.Y), color2); } private QuadBlitter.Data.Builder GetTexturedQuads() { return _texturedQuadDynamicBuilder ?? (_texturedQuadDynamicBuilder = _texturedQuadDynamicData.Map(FrameRenderContext)); } }