A game framework written with osu! in mind.
at master 296 lines 9.3 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using osu.Framework.Graphics.Primitives; 6using osu.Framework.Graphics.Textures; 7using osuTK; 8using osu.Framework.Graphics.Shaders; 9using osu.Framework.Allocation; 10using System.Collections.Generic; 11using osu.Framework.Caching; 12using osu.Framework.Extensions.EnumExtensions; 13using osuTK.Graphics; 14using osuTK.Graphics.ES30; 15 16namespace osu.Framework.Graphics.Lines 17{ 18 public partial class Path : Drawable, IBufferedDrawable 19 { 20 public IShader RoundedTextureShader { get; private set; } 21 public IShader TextureShader { get; private set; } 22 private IShader pathShader; 23 24 public Path() 25 { 26 AutoSizeAxes = Axes.Both; 27 } 28 29 [BackgroundDependencyLoader] 30 private void load(ShaderManager shaders) 31 { 32 RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 33 TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 34 pathShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE); 35 } 36 37 private readonly List<Vector2> vertices = new List<Vector2>(); 38 39 public IReadOnlyList<Vector2> Vertices 40 { 41 get => vertices; 42 set 43 { 44 vertices.Clear(); 45 vertices.AddRange(value); 46 47 vertexBoundsCache.Invalidate(); 48 segmentsCache.Invalidate(); 49 50 Invalidate(Invalidation.DrawSize); 51 } 52 } 53 54 private float pathRadius = 10f; 55 56 /// <summary> 57 /// How wide this path is on each side of the line. 58 /// </summary> 59 /// <remarks> 60 /// The actual width of the path is twice the PathRadius. 61 /// </remarks> 62 public virtual float PathRadius 63 { 64 get => pathRadius; 65 set 66 { 67 if (pathRadius == value) return; 68 69 pathRadius = value; 70 71 vertexBoundsCache.Invalidate(); 72 segmentsCache.Invalidate(); 73 74 Invalidate(Invalidation.DrawSize); 75 } 76 } 77 78 public override Axes RelativeSizeAxes 79 { 80 get => base.RelativeSizeAxes; 81 set 82 { 83 if ((AutoSizeAxes & value) != 0) 84 throw new InvalidOperationException("No axis can be relatively sized and automatically sized at the same time."); 85 86 base.RelativeSizeAxes = value; 87 } 88 } 89 90 private Axes autoSizeAxes; 91 92 /// <summary> 93 /// Controls which <see cref="Axes"/> are automatically sized w.r.t. the bounds of the vertices. 94 /// It is not allowed to manually set <see cref="Size"/> (or <see cref="Width"/> / <see cref="Height"/>) 95 /// on any <see cref="Axes"/> which are automatically sized. 96 /// </summary> 97 public virtual Axes AutoSizeAxes 98 { 99 get => autoSizeAxes; 100 set 101 { 102 if (value == autoSizeAxes) 103 return; 104 105 if ((RelativeSizeAxes & value) != 0) 106 throw new InvalidOperationException("No axis can be relatively sized and automatically sized at the same time."); 107 108 autoSizeAxes = value; 109 OnSizingChanged(); 110 } 111 } 112 113 public override float Width 114 { 115 get 116 { 117 if (AutoSizeAxes.HasFlagFast(Axes.X)) 118 return base.Width = vertexBounds.Width; 119 120 return base.Width; 121 } 122 set 123 { 124 if ((AutoSizeAxes & Axes.X) != 0) 125 throw new InvalidOperationException($"The width of a {nameof(Path)} with {nameof(AutoSizeAxes)} can not be set manually."); 126 127 base.Width = value; 128 } 129 } 130 131 public override float Height 132 { 133 get 134 { 135 if (AutoSizeAxes.HasFlagFast(Axes.Y)) 136 return base.Height = vertexBounds.Height; 137 138 return base.Height; 139 } 140 set 141 { 142 if ((AutoSizeAxes & Axes.Y) != 0) 143 throw new InvalidOperationException($"The height of a {nameof(Path)} with {nameof(AutoSizeAxes)} can not be set manually."); 144 145 base.Height = value; 146 } 147 } 148 149 public override Vector2 Size 150 { 151 get 152 { 153 if (AutoSizeAxes != Axes.None) 154 return base.Size = vertexBounds.Size; 155 156 return base.Size; 157 } 158 set 159 { 160 if ((AutoSizeAxes & Axes.Both) != 0) 161 throw new InvalidOperationException($"The Size of a {nameof(Path)} with {nameof(AutoSizeAxes)} can not be set manually."); 162 163 base.Size = value; 164 } 165 } 166 167 private readonly Cached<RectangleF> vertexBoundsCache = new Cached<RectangleF>(); 168 169 private RectangleF vertexBounds 170 { 171 get 172 { 173 if (vertexBoundsCache.IsValid) 174 return vertexBoundsCache.Value; 175 176 if (vertices.Count > 0) 177 { 178 float minX = 0; 179 float minY = 0; 180 float maxX = 0; 181 float maxY = 0; 182 183 foreach (var v in vertices) 184 { 185 minX = Math.Min(minX, v.X - PathRadius); 186 minY = Math.Min(minY, v.Y - PathRadius); 187 maxX = Math.Max(maxX, v.X + PathRadius); 188 maxY = Math.Max(maxY, v.Y + PathRadius); 189 } 190 191 return vertexBoundsCache.Value = new RectangleF(minX, minY, maxX - minX, maxY - minY); 192 } 193 194 return vertexBoundsCache.Value = new RectangleF(0, 0, 0, 0); 195 } 196 } 197 198 public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) 199 { 200 var localPos = ToLocalSpace(screenSpacePos); 201 var pathRadiusSquared = PathRadius * PathRadius; 202 203 foreach (var t in segments) 204 { 205 if (t.DistanceSquaredToPoint(localPos) <= pathRadiusSquared) 206 return true; 207 } 208 209 return false; 210 } 211 212 public Vector2 PositionInBoundingBox(Vector2 pos) => pos - vertexBounds.TopLeft; 213 214 public void ClearVertices() 215 { 216 if (vertices.Count == 0) 217 return; 218 219 vertices.Clear(); 220 221 vertexBoundsCache.Invalidate(); 222 segmentsCache.Invalidate(); 223 224 Invalidate(Invalidation.DrawSize); 225 } 226 227 public void AddVertex(Vector2 pos) 228 { 229 vertices.Add(pos); 230 231 vertexBoundsCache.Invalidate(); 232 segmentsCache.Invalidate(); 233 234 Invalidate(Invalidation.DrawSize); 235 } 236 237 private readonly List<Line> segmentsBacking = new List<Line>(); 238 private readonly Cached segmentsCache = new Cached(); 239 private List<Line> segments => segmentsCache.IsValid ? segmentsBacking : generateSegments(); 240 241 private List<Line> generateSegments() 242 { 243 segmentsBacking.Clear(); 244 245 if (vertices.Count > 1) 246 { 247 Vector2 offset = vertexBounds.TopLeft; 248 for (int i = 0; i < vertices.Count - 1; ++i) 249 segmentsBacking.Add(new Line(vertices[i] - offset, vertices[i + 1] - offset)); 250 } 251 252 segmentsCache.Validate(); 253 return segmentsBacking; 254 } 255 256 private Texture texture; 257 258 protected Texture Texture 259 { 260 get => texture ?? Texture.WhitePixel; 261 set 262 { 263 if (texture == value) 264 return; 265 266 texture?.Dispose(); 267 texture = value; 268 269 Invalidate(Invalidation.DrawNode); 270 } 271 } 272 273 public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo; 274 275 public Vector2 FrameBufferScale { get; } = Vector2.One; 276 277 // The path should not receive the true colour to avoid colour doubling when the frame-buffer is rendered to the back-buffer. 278 public override DrawColourInfo DrawColourInfo => new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 279 280 public Color4 BackgroundColour => new Color4(0, 0, 0, 0); 281 282 private readonly BufferedDrawNodeSharedData sharedData = new BufferedDrawNodeSharedData(new[] { RenderbufferInternalFormat.DepthComponent16 }); 283 284 protected override DrawNode CreateDrawNode() => new BufferedDrawNode(this, new PathDrawNode(this), sharedData); 285 286 protected override void Dispose(bool isDisposing) 287 { 288 base.Dispose(isDisposing); 289 290 texture?.Dispose(); 291 texture = null; 292 293 sharedData.Dispose(); 294 } 295 } 296}