Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Spline.cs
1using System;
2using System.Threading.Tasks;
3using System.Xml;
4using SkiaSharp;
7
9{
14 {
15 private StringAttribute head;
16 private StringAttribute tail;
17
23 public Spline(Layout2DDocument Document, ILayoutElement Parent)
24 : base(Document, Parent)
25 {
26 }
27
31 public override void Dispose()
32 {
33 base.Dispose();
34
35 this.spline?.Dispose();
36 this.spline = null;
37 }
38
42 public override string LocalName => "Spline";
43
48 {
49 get => this.head;
50 set => this.head = value;
51 }
52
57 {
58 get => this.tail;
59 set => this.tail = value;
60 }
61
66 public override Task FromXml(XmlElement Input)
67 {
68 this.head = new StringAttribute(Input, "head", this.Document);
69 this.tail = new StringAttribute(Input, "tail", this.Document);
70
71 return base.FromXml(Input);
72 }
73
78 public override void ExportAttributes(XmlWriter Output)
79 {
80 base.ExportAttributes(Output);
81
82 this.head?.Export(Output);
83 this.tail?.Export(Output);
84 }
85
92 public override ILayoutElement Create(Layout2DDocument Document, ILayoutElement Parent)
93 {
94 return new Spline(Document, Parent);
95 }
96
102 public static SKPath CreateSpline(params Vertex[] Points)
103 {
104 return CreateSpline(null, Points);
105 }
106
113 public static SKPath CreateSpline(SKPath AppendTo, params Vertex[] Points)
114 {
115 return CreateSpline(AppendTo, ToPoints(Points));
116 }
117
118 private static SKPoint[] ToPoints(Vertex[] Points)
119 {
120 int c = Points.Length;
121 int i;
122 SKPoint[] P = new SKPoint[c];
123
124 for (i = 0; i < c; i++)
125 P[i] = new SKPoint(Points[i].XCoordinate, Points[i].YCoordinate);
126
127 return P;
128 }
129
135 public static SKPath CreateSpline(params SKPoint[] Points)
136 {
137 return CreateSpline(null, Points);
138 }
139
146 public static SKPath CreateSpline(SKPath AppendTo, params SKPoint[] Points)
147 {
148 return CreateSpline(AppendTo, out _, out _, out _, out _, Points);
149 }
150
151 private static SKPath CreateSpline(SKPath AppendTo,
152 out float[] Ax, out float[] Ay, out float[] Bx, out float[] By,
153 params SKPoint[] Points)
154 {
155 int i, c = Points.Length;
156 if (c == 0)
157 throw new ArgumentException("No points provided.", nameof(Points));
158
159 if (AppendTo is null)
160 {
161 AppendTo = new SKPath();
162 AppendTo.MoveTo(Points[0]);
163 }
164 else
165 {
166 SKPoint P = AppendTo.LastPoint;
167
168 if (P.X != Points[0].X || P.Y != Points[0].Y)
169 AppendTo.LineTo(Points[0]);
170 }
171
172 if (c == 1)
173 {
174 Ax = Ay = Bx = By = null;
175 return AppendTo;
176 }
177
178 if (c == 2)
179 {
180 AppendTo.LineTo(Points[1]);
181 Ax = Ay = Bx = By = null;
182 return AppendTo;
183 }
184
185 float[] V = new float[c];
186
187 for (i = 0; i < c; i++)
188 V[i] = Points[i].X;
189
190 GetCubicBezierCoefficients(V, out Ax, out Bx);
191
192 for (i = 0; i < c; i++)
193 V[i] = Points[i].Y;
194
195 GetCubicBezierCoefficients(V, out Ay, out By);
196
197 for (i = 0; i < c - 1; i++)
198 AppendTo.CubicTo(Ax[i], Ay[i], Bx[i], By[i], Points[i + 1].X, Points[i + 1].Y);
199
200 return AppendTo;
201 }
202
209 public static void GetCubicBezierCoefficients(float[] V, out float[] A, out float[] B)
210 {
211 // Calculate Spline between points P[0], ..., P[N].
212 // Divide into segments, B[0], ...., B[N-1] of cubic Bezier curves:
213 //
214 // B[i](t) = (1-t)³P[i] + 3t(1-t)²A[i] + 3t²(1-t)B[i] + t³P[i+1]
215 //
216 // B'[i](t) = (-3+6t-3t²)P[i]+(3-12t+9t²)A[i]+(6t-9t²)B[i]+3t²P[i+1]
217 // B"[i](t) = (6-6t)P[i]+(-12+18t)A[i]+(6-18t)B[i]+6tP[i+1]
218 //
219 // Choose control points A[i] and B[i] such that:
220 //
221 // B'[i](1) = B'[i+1](0) => A[i+1]+B[i]=2P[i+1], i<N (eq 1)
222 // B"[i](1) = B"[i+1](0) => A[i]-2B[i]+2A[i+1]-B[i+1]=0 (eq 2)
223 //
224 // Also add the boundary conditions:
225 //
226 // B"[0](0)=0 => 2A[0]-B[0]=P[0] (eq 3)
227 // B"[N-1](1)=0 => -A[N-1]+2B[N-1]=P[N] (eq 4)
228 //
229 // Method solves this linear equation for one coordinate of A[i] and B[i] at a time.
230 //
231 // First, the linear equation, is reduced downwards. Only coefficients close to
232 // the diagonal, and in the right-most column need to be processed. Furthermore,
233 // we don't have to store values we know are zero or one. Since number of operations
234 // depend linearly on number of vertices, algorithm is O(N).
235 //
236 // Matrix of system of linear equations has the following form (zeroes excluded):
237 //
238 // | A0 B0 A1 B1 A2 B2 A3 B3 ... AN BN | EQ |
239 // |-----------------------------------|-----|
240 // | 2 -1 | P0 | (eq 3)
241 // | 1 -2 2 -1 | 0 | (eq 2)
242 // | 1 1 | 2P1 | (eq 1)
243 // | 1 -2 2 -1 | 0 | (eq 2)
244 // | 1 1 | 2P2 | (eq 1)
245 // | 1 -2 2 -1 | 0 | (eq 2)
246 // | 1 1 | 2P3 | (eq 1)
247 // | ... | . |
248 // | ... | . |
249 // | ... | . |
250 // | -1 2 | PN | (eq 4)
251
252 int N = V.Length - 1;
253 int N2 = N << 1;
254 int i = 0;
255 int j = 0;
256 float r11, r12, r15; // r13 & r14 always 0.
257 float r22, r23, r25; // r21 & r24 always 0 for all except last equation, where r21 is -1.
258 float /*r31,*/ r32, r33, r34, r35;
259 float[,] Rows = new float[N2, 3];
260 float a;
261
262 A = new float[N];
263 B = new float[N];
264
265 r11 = 2; // eq 3
266 r12 = -1;
267 r15 = V[j++];
268
269 r22 = 1; // eq 1
270 r23 = 1;
271 r25 = 2 * V[j++];
272
273 // r31 = 1; // eq 2
274 r32 = -2;
275 r33 = 2;
276 r34 = -1;
277 r35 = 0;
278
279 while (true)
280 {
281 a = 1 / r11;
282 // r11 = 1;
283 r12 *= a;
284 r15 *= a;
285
286 // r21 is always 0. No need to eliminate column.
287 // r22 is always 1. No need to scale row.
288
289 // r31 is always 1 at this point.
290 // r31 -= r11;
291 r32 -= r12;
292 r35 -= r15;
293
294 if (r32 != 0)
295 {
296 r33 -= r32 * r23;
297 r35 -= r32 * r25;
298 // r32 = 0;
299 }
300
301 // r33 is always 0.
302
303 // r11 always 1.
304 Rows[i, 0] = r12;
305 Rows[i, 1] = 0;
306 Rows[i, 2] = r15;
307 i++;
308
309 // r21, r24 always 0.
310 Rows[i, 0] = r22;
311 Rows[i, 1] = r23;
312 Rows[i, 2] = r25;
313 i++;
314
315 if (i >= N2 - 2)
316 break;
317
318 r11 = r33;
319 r12 = r34;
320 r15 = r35;
321
322 r22 = 1; // eq 1
323 r23 = 1;
324 r25 = 2 * V[j++];
325
326 // r31 = 1; // eq 2
327 r32 = -2;
328 r33 = 2;
329 r34 = -1;
330 r35 = 0;
331 }
332
333 r11 = r33;
334 r12 = r34;
335 r15 = r35;
336
337 //r21 = -1; // eq 4
338 r22 = 2;
339 r23 = 0;
340 r25 = V[j++];
341
342 a = 1 / r11;
343 //r11 = 1;
344 r12 *= a;
345 r15 *= a;
346
347 //r21 += r11;
348 r22 += r12;
349 r25 += r15;
350
351 r25 /= r22;
352 r22 = 1;
353
354 // r11 always 1.
355 Rows[i, 0] = r12;
356 Rows[i, 1] = 0;
357 Rows[i, 2] = r15;
358 i++;
359
360 // r21 and r24 always 0.
361 Rows[i, 0] = r22;
362 Rows[i, 1] = r23;
363 Rows[i, 2] = r25;
364 i++;
365
366 // Then eliminate back up:
367
368 j--;
369 while (i > 0)
370 {
371 i--;
372 if (i < N2 - 1)
373 {
374 a = Rows[i, 1];
375 if (a != 0)
376 {
377 Rows[i, 1] = 0;
378 Rows[i, 2] -= a * Rows[i + 1, 2];
379 }
380 }
381
382 B[--j] = Rows[i, 2];
383
384 i--;
385 a = Rows[i, 0];
386 if (a != 0)
387 {
388 Rows[i, 0] = 0;
389 Rows[i, 2] -= a * Rows[i + 1, 2];
390 }
391
392 A[j] = Rows[i, 2];
393 }
394 }
395
400 public override void CopyContents(ILayoutElement Destination)
401 {
402 base.CopyContents(Destination);
403
404 if (Destination is Spline Dest)
405 {
406 Dest.head = this.head?.CopyIfNotPreset(Destination.Document);
407 Dest.tail = this.tail?.CopyIfNotPreset(Destination.Document);
408 }
409 }
410
416 public override async Task DoMeasureDimensions(DrawingState State)
417 {
418 await base.DoMeasureDimensions(State);
419
420 EvaluationResult<string> RefId = await this.head.TryEvaluate(State.Session);
421 if (RefId.Ok &&
422 this.Document.TryGetElement(RefId.Result, out ILayoutElement Element) &&
423 Element is Shape Shape)
424 {
425 this.headElement = Shape;
426 }
427
428 RefId = await this.head.TryEvaluate(State.Session);
429 if (RefId.Ok &&
430 this.Document.TryGetElement(RefId.Result, out Element) &&
431 Element is Shape Shape2)
432 {
433 this.tailElement = Shape2;
434 }
435
436 this.spline?.Dispose();
437 this.spline = null;
438 }
439
440 private Shape headElement;
441 private Shape tailElement;
442 private SKPath spline;
443 private float[] ax;
444 private float[] ay;
445 private float[] bx;
446 private float[] by;
447
452 public override async Task Draw(DrawingState State)
453 {
454 if (this.defined)
455 {
456 SKPaint Pen = await this.GetPen(State);
457
458 this.CheckSpline();
459
460 State.Canvas.DrawPath(this.spline, Pen);
461
462 if (!(this.tailElement is null) || !(this.headElement is null))
463 {
464 SKPaint Fill = await this.TryGetFill(State);
465
466 this.tailElement?.DrawTail(State, this, Pen, Fill);
467 this.headElement?.DrawHead(State, this, Pen, Fill);
468 }
469 }
470
471 await base.Draw(State);
472 }
473
474 private void CheckSpline()
475 {
476 if (this.spline is null)
477 {
478 this.spline = CreateSpline(null, out this.ax, out this.ay,
479 out this.bx, out this.by, ToPoints(this.points));
480 }
481 }
482
490 public bool TryGetStart(out float X, out float Y, out float Direction)
491 {
492 int c;
493
494 if (this.defined && !(this.points is null) && (c = this.points.Length) >= 2)
495 {
496 Vertex P1 = this.points[0];
497
498 X = P1.XCoordinate;
499 Y = P1.YCoordinate;
500
501 if (c == 2)
502 {
503 Vertex P2 = this.points[1];
504
505 Direction = CalcDirection(P1, P2);
506 }
507 else
508 {
509 // B'[i](t) = (-3+6t-3t²)P[i]+(3-12t+9t²)A[i]+(6t-9t²)B[i]+3t²P[i+1]
510 // B'[i](0) = -3P[i]+3A[i]
511
512 float dx = 3 * (this.ax[0] - P1.XCoordinate);
513 float dy = 3 * (this.ay[0] - P1.YCoordinate);
514
515 Direction = CalcDirection(dx, dy);
516 }
517
518 return true;
519 }
520
521 X = Y = Direction = 0;
522 return false;
523 }
524
532 public bool TryGetEnd(out float X, out float Y, out float Direction)
533 {
534 int c;
535
536 if (this.defined && !(this.points is null) && (c = this.points.Length) >= 2)
537 {
538 Vertex P2 = this.points[c - 1];
539
540 X = P2.XCoordinate;
541 Y = P2.YCoordinate;
542
543 if (c == 2)
544 {
545 Vertex P1 = this.points[c - 2];
546
547 Direction = CalcDirection(P1, P2);
548 }
549 else
550 {
551 // B'[i](t) = (-3+6t-3t²)P[i]+(3-12t+9t²)A[i]+(6t-9t²)B[i]+3t²P[i+1]
552 // B'[i](1) = -3B[i]+3P[i+1]
553
554 float dx = 3 * (P2.XCoordinate - this.bx[c - 2]);
555 float dy = 3 * (P2.YCoordinate - this.by[c - 2]);
556
557 Direction = CalcDirection(dx, dy);
558 }
559
560 return true;
561 }
562
563 X = Y = Direction = 0;
564 return false;
565 }
566
571 public override void ExportStateAttributes(XmlWriter Output)
572 {
573 base.ExportStateAttributes(Output);
574
575 this.head?.ExportState(Output);
576 this.tail?.ExportState(Output);
577 }
578
579 }
580}
Contains a 2D layout document.
void Export(XmlWriter Output)
Exports the attribute.
Definition: Attribute.cs:187
void ExportState(XmlWriter Output)
Exports the state of the attribute.
Definition: Attribute.cs:199
static async Task< EvaluationResult< T > > TryEvaluate(Attribute< T > Attribute, Variables Session)
Tries to evaluate the attribute value.
Definition: Attribute.cs:256
StringAttribute CopyIfNotPreset(Layout2DDocument ForDocument)
Copies the attribute object if undefined, or defined by an expression. Returns a reference to itself,...
SKCanvas Canvas
Current drawing canvas.
Variables Session
Current session.
Definition: DrawingState.cs:91
async Task< SKPaint > TryGetFill(DrawingState State)
Tries to get the filling of the figure, if one is defined.
Definition: Figure.cs:130
async Task< SKPaint > GetPen(DrawingState State)
Gets the pen associated with the element. If not found, the default pen is returned.
Definition: Figure.cs:90
override void ExportStateAttributes(XmlWriter Output)
Exports the local attributes of the current element.
Definition: Spline.cs:571
override ILayoutElement Create(Layout2DDocument Document, ILayoutElement Parent)
Creates a new instance of the layout element.
Definition: Spline.cs:92
override string LocalName
Local name of type of element.
Definition: Spline.cs:42
static SKPath CreateSpline(SKPath AppendTo, params Vertex[] Points)
Creates a Spline path through a given set of points.
Definition: Spline.cs:113
static SKPath CreateSpline(SKPath AppendTo, params SKPoint[] Points)
Creates a Spline path through a given set of points.
Definition: Spline.cs:146
static void GetCubicBezierCoefficients(float[] V, out float[] A, out float[] B)
Gets a set of coefficients for cubic Bezier curves, forming a spline, one coordinate at a time.
Definition: Spline.cs:209
bool TryGetStart(out float X, out float Y, out float Direction)
Tries to get start position and initial direction.
Definition: Spline.cs:490
override void CopyContents(ILayoutElement Destination)
Copies contents (attributes and children) to the destination element.
Definition: Spline.cs:400
static SKPath CreateSpline(params Vertex[] Points)
Creates a Spline path through a given set of points.
Definition: Spline.cs:102
bool TryGetEnd(out float X, out float Y, out float Direction)
Tries to get end position and terminating direction.
Definition: Spline.cs:532
override async Task Draw(DrawingState State)
Draws layout entities.
Definition: Spline.cs:452
override async Task DoMeasureDimensions(DrawingState State)
Measures layout entities and defines unassigned properties, related to dimensions.
Definition: Spline.cs:416
override Task FromXml(XmlElement Input)
Populates the element (including children) with information from its XML definition.
Definition: Spline.cs:66
static SKPath CreateSpline(params SKPoint[] Points)
Creates a Spline path through a given set of points.
Definition: Spline.cs:135
Spline(Layout2DDocument Document, ILayoutElement Parent)
A spline
Definition: Spline.cs:23
override void ExportAttributes(XmlWriter Output)
Exports attributes to XML.
Definition: Spline.cs:78
override void Dispose()
IDisposable.Dispose
Definition: Spline.cs:31
Layout2DDocument Document
Layout document.
bool defined
If element is well-defined.
static float CalcDirection(float x1, float y1, float x2, float y2)
Calculates the direction of the pen when drawing from (x1,y1) to (x2,y2).
Defines a shape for use elsewhere in the layout.
Definition: Shape.cs:11
Task DrawTail(DrawingState State, IDirectedElement DirectedElement, SKPaint DefaultPen, SKPaint DefaultFill)
Draws shape as a tail
Definition: Shape.cs:98
Task DrawHead(DrawingState State, IDirectedElement DirectedElement, SKPaint DefaultPen, SKPaint DefaultFill)
Draws shape as a head
Definition: Shape.cs:114
Defines a vertex in the graf
Definition: Vertex.cs:7
float YCoordinate
Measured Y-coordinate
Definition: Vertex.cs:42
float XCoordinate
Measured X-coordinate
Definition: Vertex.cs:37
Interface for directed elements.
Base interface for all layout elements.
Layout2DDocument Document
Layout document.