2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Class and routines to output isolines.
8
25/04/15 - PW - Fixed TriangleElevationSegments procedure
9
06/07/02 - Phil Scadden - Added TContour class and Initialise_Isolining procedure
10
15/08/01 - Alexander Weidauer - Added CONREC Delphi implementation
11
(based on Nicholas Yue CONREC.C and Paul D. Bourke CONREC.F)
12
15/07/01 - PW - Creation of the unit
19
System.SysUtils, System.Classes, System.Math,
21
GLVectorGeometry, GLVectorLists, GLTypes, GLSpline;
26
TVectorL4D = array [0 .. 4] of Single;
27
TVectorL4I = array [0 .. 4] of Integer;
28
TCastArray = array [0 .. 2, 0 .. 2, 0 .. 2] of Integer;
30
TGLIsoline2D = array [0 .. 32767] of TGLPoint2D;
31
PGLIsoline2D = ^TGLIsoline2D;
36
constructor Create(LineSize: integer); virtual;
37
destructor Destroy; override;
41
TIsolineState = (ilsEmpty, ilsCalculating, ilsReady);
46
IsolineState: TIsolineState;
47
procedure MakeIsolines(var Depths: TGLMatrix; bmSize: Integer;
48
StartDepth, EndDepth: Single; Interval: Integer);
50
constructor Create; virtual;
51
destructor Destroy; override;
54
procedure Initialize_Isolining(var DataGrid: TGLMatrix;
55
NXpoints, NYpoints: integer; CurrentIsoline: Single);
56
procedure Release_Memory_Isolining;
57
function GetNextIsoline(var Isoline: TIsoline): Boolean;
59
{ : Defines contouring segments inside a triangle using elevations }
60
procedure TriangleElevationSegments(const p1, p2, p3: TAffineVector;
61
ElevationDelta: Single; Segments: TAffineVectorList);
63
{ : CONREC is a contouring routine for rectangular spaced data or regular 2D grids
64
It takes each rectangle of adjacent data points and splits it
65
into 4 triangles after choosing the height at the centre of the rectangle.
66
For each of the triangles the line segment resulting from the intersection
67
with each contour plane.
68
A routine is then called with the starting and stopping coordinates
69
of the line segment that make up a contour curve and then output these
70
isolines. See details in http://paulbourke.net/papers/conrec/
72
The input parameters are as follows :
73
Data - Scalar field in 2D grid
74
ilb - lower bound in west - east direction
75
iub - upper bound in west - east direction
76
jlb - lower bound in north - south direction
77
jub upper bound in north - south direction
78
X - coord. vector for west - east
79
Y - coord. vector for north - south
80
nc - number of cut levels
81
Z - values of cut levels
83
procedure Conrec(Data: TGLMatrix; ilb, iub, jlb, jub: Integer;
84
x: TGLVector; y: TGLVector; nc: Integer; z: TGLVector; Isoline: TIsoline);
87
//----------------------------------------------------------------------
88
//----------------------------------------------------------------------
89
//----------------------------------------------------------------------
91
//----------------------------------------------------------------------
92
//----------------------------------------------------------------------
93
//----------------------------------------------------------------------
97
Visited: TGLByteMatrix; // 0 = not visited
99
// if it is a saddle points, then bits 1-4 also encode
100
// which exit and entry points were used.
103
LineX1, LineY1, LineX2, LineY2: TGLVector;
105
function EqAdd(a, b: integer): integer;
113
// Initialize_Isolining
115
procedure Initialize_Isolining;
125
maxnp := NX * NY div 256;
126
SetLength(Visited, NX, NY);
127
for i := 0 to NX - 1 do
128
for j := 0 to NY - 1 do
130
SetLength(Grid, NX + 1, NY + 1);
131
SetLength(LineX1, maxnp);
132
SetLength(LineY1, maxnp);
133
SetLength(LineX2, maxnp);
134
SetLength(LineY2, maxnp);
135
// Generate a grid of data relative to the Isoline level
140
Grid[i][j] := DataGrid[i - 1][j - 1] - CurrentIsoline;
141
(* Don't want any grid points exactly zero *)
142
if Grid[i][j] = 0 then
150
// Release_Memory_Isolining
152
procedure Release_Memory_Isolining;
154
SetLength(Visited, 0);
156
SetLength(LineX1, 0);
157
SetLength(LineY1, 0);
158
SetLength(LineX2, 0);
159
SetLength(LineY2, 0);
164
procedure Cuts(const g: TGLMatrix; i, j: integer; var s: array of integer);
167
if g[i][j + 1] * g[i + 1][j + 1] < 0 then
172
if g[i + 1][j + 1] * g[i + 1][j] < 0 then
177
if g[i + 1][j] * g[i][j] < 0 then
182
if g[i][j] * g[i][j + 1] < 0 then
191
procedure Intercept(const g: TGLMatrix; i, j, s: Integer;
197
x := abs(g[i][j + 1] / (g[i + 1][j + 1] - g[i][j + 1])) + i;
202
y := abs(g[i + 1][j] / (g[i + 1][j + 1] - g[i + 1][j])) + j;
207
x := abs(g[i][j] / (g[i + 1][j] - g[i][j])) + i;
212
y := abs(g[i][j] / (g[i][j + 1] - g[i][j])) + j;
220
function Free_Exit(const Visited: TGLByteMatrix;
221
i, j, NX, NY, Lexit: Integer): Boolean;
226
nj := j + EqAdd(lexit, 1) - EqAdd(lexit, 3);
227
ni := i + EqAdd(lexit, 2) - EqAdd(lexit, 4);
228
if (ni < 1) or (ni >= NX) or (nj < 1) or (nj >= NY) or (Visited[ni][nj] = 0)
230
Result := True // can always exit on an edge
233
entry := ((lexit + 1) mod 4) + 1;
234
Result := (((Visited[ni][nj] shr entry) and 1) = 0);
240
procedure TraceIsoline(i, j, Lexit, NX, NY: Integer; const Grid: TGLMatrix;
241
const Visited: TGLByteMatrix; var LineX, LineY: TGLVector;
242
var NP: Integer; var OffGrid: Boolean);
244
ni, nj, si, sj: Integer;
246
s: array [0 .. 5] of Integer;
257
Intercept(Grid, i, j, lexit, LineX[NP], LineY[NP]);
261
nj := nj + EqAdd(lexit, 1) - EqAdd(lexit, 3);
262
ni := ni + EqAdd(lexit, 2) - EqAdd(lexit, 4);
263
if (ni < 1) or (ni > NX - 1) or (nj < 1) or (nj > NY - 1) then
268
Visited[ni][nj] := 1;
269
entry := ((lexit + 1) mod 4) + 1;
270
Cuts(Grid, ni, nj, s);
271
// Have come to a new point on the Isoline
275
// If there are two cuts then choose the one that is not the entry
283
// If there are four cuts (saddle) then work round from the left
284
p := (entry mod 4) + 1;
287
for q := 1 to s[0] do
289
if (s[q] = p) and Free_Exit(Visited, NX, NY, ni, nj, p) then
295
// Aim is to find first
300
(* exit from cell, going *)
301
// We found a way out, make a note of way in and way out.
302
// Need to do this as saddle may be visited twice.
303
Visited[ni][nj] := (Visited[ni][nj]) or (1 shl entry) or (1 shl lexit);
305
// clockwise from entry point
306
Assert(lexit > 0, 'Contour routine caught in a loop');
309
Intercept(Grid, ni, nj, lexit, LineX[NP], LineY[NP]);
311
if (ni = si) and (nj = sj) then
314
// Have finished loop
317
{ LineX and LineY are (pointers to) zero-offset vectors, to which
318
sufficient space has been allocated to store the coordinates of
319
any feasible Isoline }
321
function GetNextIsoline(var Isoline: TIsoline): Boolean;
327
s: array [0 .. 4] of integer;
329
for i := ii to NX - 1 do
331
for j := 1 + (jj - 1) * EqAdd(i, ii) to NY - 1 do
333
if (Visited[i][j] = 0) then
339
TraceIsoline(i, j, lexit, NX, NY, Grid, Visited, LineX1, LineY1,
341
// Follow the Isoline along
344
// Go back to start of Isoline and trace in opposite direction
346
TraceIsoline(i, j, Lexit, NX, NY, Grid, Visited, LineX2, LineY2,
348
// Copy both bits of line into Isoline
349
Isoline := TIsoline.Create(np1 + np2);
350
for k := 0 to np2 - 1 do
352
Isoline.Line^[k].x := LineX2[np2 - k - 1];
353
Isoline.Line^[k].y := LineY2[np2 - k - 1];
355
for k := 0 to np1 - 1 do
357
Isoline.Line^[k + np2].x := LineX1[k];
358
Isoline.Line^[k + np2].y := LineY1[k];
363
// Just copy the single Isoline loop into LineX & LineY
364
Isoline := TIsoline.Create(np1);
365
for k := 0 to np1 - 1 do
367
Isoline.Line^[k].x := LineX1[k];
368
Isoline.Line^[k].y := LineY1[k];
371
// scale Isoline into true units
374
LineX[k-1]:= xlo+(LineX[k]-1)*(xhi-xlo) / (nx-1);
375
LineY[k-1]:= ylo+(LineY[k]-1)*(yhi-ylo) / (ny-1);
376
// LineX and LineY are zero offset vectors
389
// TriangleElevationSegments
391
procedure TriangleElevationSegments(const p1, p2, p3: TAffineVector;
392
ElevationDelta: Single; Segments: TAffineVectorList);
394
function SegmentIntersect(const a, b: TAffineVector; e: Single): Integer;
400
if (e >= a.Z) and (e < b.Z) then
402
f := (e - a.Z) / (b.Z - a.Z);
403
Segments.Add(VectorLerp(a, b, f));
409
else if a.Z > b.Z then
411
if (e > b.Z) and (e <= a.Z) then
413
f := (e - b.Z) / (a.Z - b.Z);
414
Segments.Add(VectorLerp(b, a, f));
425
i, n, LowElev, HighElev: Integer;
429
LowElev := Round(MinFloat(p1.Z, p2.Z, p3.Z) / ElevationDelta - 0.5);
430
HighElev := Round(MaxFloat(p1.Z, p2.Z, p3.Z) / ElevationDelta + 0.5);
431
for i := LowElev to HighElev do
433
e := i * ElevationDelta + 0.1;
434
// add a real offset - this avoids all the special cases
435
n := SegmentIntersect(p1, p2, e);
437
n := n + SegmentIntersect(p2, p3, e);
439
n := n + SegmentIntersect(p3, p1, e);
440
Assert((n = 2) or (n = 0));
446
constructor TIsolines.Create;
449
LineList := TList.Create;
450
IsolineState := ilsEmpty;
453
destructor TIsolines.Destroy;
459
procedure TIsolines.FreeList;
463
for i := LineList.Count - 1 downto 0 do
465
with TIsoline(LineList.Items[i]) do
469
IsolineState := ilsEmpty;
472
procedure TIsolines.MakeIsolines(var Depths: TGLMatrix; bmSize: integer;
473
StartDepth, EndDepth: Single; Interval: Integer);
478
IsolineState := ilsCalculating;
479
CoordRange := bmSize;
482
Initialize_Isolining(Depths, bmSize, bmSize, StartDepth);
483
while GetNextIsoline(Isoline) do
485
LineList.Add(Isoline);
487
Release_Memory_Isolining;
488
StartDepth := StartDepth + Interval;
489
until StartDepth > EndDepth;
490
IsolineState := ilsReady;
493
constructor TIsoline.Create(LineSize: integer);
497
Getmem(Line, NP * 2 * Sizeof(Single));
500
destructor TIsoline.Destroy;
503
if Assigned(Line) then
510
procedure Conrec(Data: TGLMatrix; ilb, iub, jlb, jub: Integer;
511
x: TGLVector; y: TGLVector; nc: Integer; z: TGLVector; Isoline: TIsoline);
512
// ------------------------------------------------------------------------------
514
im: array [0 .. 3] of Integer = (0, 1, 1, 0); // coord. cast array west - east
515
jm: array [0 .. 3] of Integer = (0, 0, 1, 1);
516
// coord. cast array north - south
517
// ------------------------------------------------------------------------------
521
dmin, dmax, x1, x2, y1, y2: Single;
522
lcnt, i, j, k, m: Integer;
527
temp1, temp2: Single;
530
// ------- service xsec west east lin. interpol -------------------------------
531
function Xsec(p1, p2: Integer): Single;
533
Result := (h[p2] * xh[p1] - h[p1] * xh[p2]) / (h[p2] - h[p1]);
536
// ------- service ysec north south lin interpol -------------------------------
537
function Ysec(p1, p2: Integer): Single;
539
Result := (h[p2] * yh[p1] - h[p1] * yh[p2]) / (h[p2] - h[p1]);
544
CastTab[0, 0, 0] := 0;
545
CastTab[0, 0, 1] := 0;
546
CastTab[0, 0, 2] := 8;
547
CastTab[0, 1, 0] := 0;
548
CastTab[0, 1, 1] := 2;
549
CastTab[0, 1, 2] := 5;
550
CastTab[0, 2, 0] := 7;
551
CastTab[0, 2, 1] := 6;
552
CastTab[0, 2, 2] := 9;
554
CastTab[1, 0, 0] := 0;
555
CastTab[1, 0, 1] := 3;
556
CastTab[1, 0, 2] := 4;
557
CastTab[1, 1, 0] := 1;
558
CastTab[1, 1, 1] := 3;
559
CastTab[1, 1, 2] := 1;
560
CastTab[1, 2, 0] := 4;
561
CastTab[1, 2, 1] := 3;
562
CastTab[1, 2, 2] := 0;
564
CastTab[2, 0, 0] := 9;
565
CastTab[2, 0, 1] := 6;
566
CastTab[2, 0, 2] := 7;
567
CastTab[2, 1, 0] := 5;
568
CastTab[2, 1, 1] := 2;
569
CastTab[2, 1, 2] := 0;
570
CastTab[2, 2, 0] := 8;
571
CastTab[2, 2, 1] := 0;
572
CastTab[2, 2, 2] := 0;
576
// -----------------------------------------------------------------------------
577
for j := jub - 1 downto jlb do
578
begin // over all north - south and +for j
579
for i := ilb to iub - 1 do
580
begin // east - west coordinates of datafield +for i
581
// set casting bounds from array
582
temp1 := Min(Data[i, j], Data[i, j + 1]);
583
temp2 := Min(Data[i + 1, j], Data[i + 1, j + 1]);
584
dmin := Min(temp1, temp2);
585
temp1 := Max(Data[i, j], Data[i, j + 1]);
586
temp2 := Max(Data[i + 1, j], Data[i + 1, j + 1]);
587
dmax := Max(temp1, temp2);
588
if (dmax >= z[0]) and (dmin <= z[nc - 1]) then
589
begin // ask horizontal cut avail. +If dmin && dmax in z[0] .. z[nc-1]
590
for k := 0 to nc - 1 do
591
begin // over all possible cuts ---- +for k
592
if (z[k] > dmin) and (z[k] <= dmax) then
593
begin // aks for cut intervall ----- +If z[k] in dmin .. dmax
594
// -----------------------------------------------------------------------
595
for m := 4 downto 0 do
596
begin // deteriening the cut casts and set the ---- +for m
598
begin // height and coordinate vectors
599
h[m] := Data[i + im[m - 1], j + jm[m - 1]] - z[k];
600
xh[m] := x[i + im[m - 1]];
601
yh[m] := y[j + jm[m - 1]];
605
h[0] := (h[1] + h[2] + h[3] + h[4]) / 4;
606
xh[0] := (x[i] + x[i + 1]) / 2;
607
yh[0] := (y[j] + y[j + 1]) / 2;
608
end; // if m>0 then else
611
else If h[m] < 0 then
617
// -----------------------------------------------------------
619
begin // set directional CastTable
621
// Note: at this stage the relative heights of the corners and the
622
// centre are in the h array, and the corresponding coordinates are
623
// in the xh and yh arrays. The centre of the box is indexed by 0
624
// and the 4 corners by 1 to 4 as shown below.
625
// Each triangle is then indexed by the parameter m, and the 3
626
// vertices of each triangle are indexed by parameters m1,m2,and
628
// It is assumed that the centre of the box is always vertex 2
629
// though this isimportant only when all 3 vertices lie exactly on
630
// the same contour level, in which case only the side of the box
633
// AS ANY BODY NOWS IST FROM THE ORIGINAL
635
// vertex 4 +-------------------+ vertex 3
640
// | m=2 X m=2 | the centre is vertex 0
645
// vertex 1 +-------------------+ vertex 2
648
// Scan each triangle in the box
656
Deside := CastTab[sh[m1] + 1, sh[m2] + 1, sh[m3] + 1];
657
if not(Deside = 0) then
658
begin // ask is there a desition available ---+if if not(Deside=0)
660
// ---- determin the by desided cast cuts ---- +Case deside;
724
end; // --- -Case deside;
725
// -------Output of results ---------------------
726
GetNextIsoline(Isoline);
727
//Writeln(Format('%2.2f %2.2f %2.2f %2.2f %2.2f', [z[k], x1, y1, x2, y2]));
728
// ---------------------------------------------------------
729
end; // ---- -if Not(deside=0)
731
end; // ---- -if z[k] in dmin .. dmax
733
end; // ---- -if dmin && dmax in z[0] .. z[nc-1]
737
// ------ End of ----------------------------------------------------------------