-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathquadtree.m
703 lines (593 loc) · 22.1 KB
/
quadtree.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
function [p,t,h] = quadtree(node,edge,hdata,dhmax,output)
% QUADTREE: 2D quadtree decomposition of polygonal geometry.
%
% The polygon is first rotated so that the minimal enclosing rectangle is
% aligned with the Cartesian XY axes. The long axis is aligned with Y. This
% ensures that the quadtree generated for a geometry input that has
% undergone arbitrary rotations in the XY plane is always the same.
%
% The bounding box is recursively subdivided until the dimension of each
% box matches the local geometry feature size. The geometry feature size is
% based on the minimum distance between linear geometry segments.
%
% A size function is obtained at the quadtree vertices based on the minimum
% neighbouring box dimension at each vertex. This size function is gradient
% limited to produce a smooth function.
%
% The quadtree is triangulated to form a background mesh, such that the
% size function may be obtained at any XY position within the domain via
% triangle based linear interpolation. The triangulation is done based on
% the quadtree data structures directly (i.e. NOT using Qhull) which is
% more reliable and produces a consistently oriented triangulation.
%
% The initial rotations are undone.
%
% node : [x1,y1; x2,y2; etc] geometry vertices
% edge : [n11,n12; n21,n22; etc] geometry edges as connections in NODE
% hdata : User defined size function structure
% dhmax : Maximum allowalble relative gradient in the size function
% wbar : Handle to progress bar from MESH2D
%
% p : Background mesh nodes
% t : Background mesh triangles
% h : Size function value at p
% Darren Engwirda : 2007
% Email : [email protected]
% Last updated : 18/11/2007 with MATLAB 7.0
% Bounding box
XYmax = max(node,[],1);
XYmin = min(node,[],1);
% Rotate NODE so that the long axis of the minimum bounding rectangle is
% aligned with the Y axis.
theta = minrectangle(node);
node = rotate(node,theta);
% Rotated XY edge endpoints
edgexy = [node(edge(:,1),:), node(edge(:,2),:)];
% LOCAL FEATURE SIZE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
fprintf('Estimating local geometry feature size\n');
end
% Get size function data
[hmax,edgeh,fun,args] = gethdata(hdata);
% Insert test points along the boundaries at which the LFS can be
% approximated.
wm = 0.5*(edgexy(:,[1,2])+edgexy(:,[3,4])); % Use the edge midpoints as a first pass
len = sqrt(sum((edgexy(:,[3,4])-edgexy(:,[1,2])).^2,2)); % Edge length
L = 2.0*dist2poly(wm,edgexy,2.0*len); % Estimate the LFS at these points by calculating
% the distance to the closest edge segment
% In cases where edges are separated by less than their length
% we will need to add more points to capture the LFS in these
% regions. This allows us to pick up "long and skinny" geometry
% features
r = 2.0*len./L; % Compare L (LFS approximation at wm) to the edge lengths
r = round((r-2.0)/2.0); % Amount of points that need to be added
add = find(r); % at each edge
if ~isempty(add)
num = 2*sum(r(add)); % Total number of points to be added
start = size(wm,1)+1;
wm = [wm; zeros(num,2)]; % Alloc space
L = [L; zeros(num,1)];
next = start;
for j = 1:length(add) % Loop through edges to be subdivided
ce = add(j); % Current edge
num = r(ce);
tmp = (1:num)'/(num+1); % Subdivision increments
num = next+2*num-1;
x1 = edgexy(ce,1); x2 = edgexy(ce,3); xm = wm(ce,1); % Edge values
y1 = edgexy(ce,2); y2 = edgexy(ce,4); ym = wm(ce,2);
wm(next:num,:) = [x1+tmp*(xm-x1), y1+tmp*(ym-y1) % Add to list
xm+tmp*(x2-xm), ym+tmp*(y2-ym)];
L(next:num) = L(ce); % Upper bound on LFS
next = num+1;
end
L(start:end) = dist2poly(wm(start:end,:),edgexy,L(start:end)); % Estimate LFS at the new points
end
% Compute the required size along the edges for any boundary layer size
% functions and add additional points where necessary.
if ~isempty(edgeh)
for j = 1:size(edgeh,1)
if L(edgeh(j,1))>=edgeh(j,2)
cw = edgeh(j,1);
r = 2.0*len(cw)/edgeh(j,2);
r = ceil((r)/2.0); % Number of points to be added
tmp = (1:r-1)'/r;
x1 = edgexy(cw,1); x2 = edgexy(cw,3); xm = wm(cw,1); % Edge values
y1 = edgexy(cw,2); y2 = edgexy(cw,4); ym = wm(cw,2);
wm = [wm; x1+tmp*(xm-x1), y1+tmp*(ym-y1); % Add to list
xm+tmp*(x2-xm), ym+tmp*(y2-ym)];
L(cw) = edgeh(j,2); % Update LFS
L = [L; edgeh(j,2)*ones(2*length(tmp),1)];
end
end
end
% To speed the point location in the quadtree decomposition
% sort the LFS points based on y-value
[i,i] = sort(wm(:,2));
wm = wm(i,:);
L = L(i);
nw = size(wm,1);
% UNBALANCED QUADTREE DECOMPOSITION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
fprintf('Quadtree decomposition\n');
end
xymin = min([edgexy(:,[1,2]); edgexy(:,[3,4])]); % Bounding box
xymax = max([edgexy(:,[1,2]); edgexy(:,[3,4])]);
dim = 2.0*max(xymax-xymin); % Bbox dimensions
xm = 0.5*(xymin(1)+xymax(1));
ym = 0.5*(xymin(2)+xymax(2));
% Setup boxes with a consistent CCW node order
% b(k,1) = n1 : bottom left
% b(k,2) = n2 : bottom right
% b(k,3) = n3 : top right
% b(k,4) = n4 : top left
% Start with bbox
p = [xm-0.5*dim, ym-0.5*dim
xm+0.5*dim, ym-0.5*dim
xm+0.5*dim, ym+0.5*dim
xm-0.5*dim, ym+0.5*dim];
b = [1,2,3,4];
% User defined size functions
pr = rotate(p,-theta);
h = userhfun(pr(:,1),pr(:,2),fun,args,hmax,XYmin,XYmax);
pblock = 5*nw; % Alloc memory in blocks
bblock = pblock;
np = size(p,1);
nb = size(b,1);
test = true(nb,1);
while true
vec = find(test(1:nb)); % Boxes to be checked at this step
if isempty(vec)
break
end
N = np;
for k = 1:length(vec) % Loop through boxes to be checked for subdivision
m = vec(k); % Current box
n1 = b(m,1); n2 = b(m,2); % Corner nodes
n3 = b(m,3); n4 = b(m,4);
x1 = p(n1,1); y1 = p(n1,2); % Corner xy
x2 = p(n2,1); y4 = p(n4,2);
% Binary search to find first wm with y>=ymin for current box
if wm(1,2)>=y1
start = 1;
elseif wm(nw,2)<y1
start = nw+1;
else
lower = 1;
upper = nw;
for i = 1:nw
start = round(0.5*(lower+upper));
if wm(start,2)<y1
lower = start;
elseif wm(start-1,2)<y1
break;
else
upper = start;
end
end
end
% Init LFS as the min of corner user defined size function values
LFS = 1.5*h(n1);
if 1.5*h(n2)<LFS, LFS = 1.5*h(n2); end
if 1.5*h(n3)<LFS, LFS = 1.5*h(n3); end
if 1.5*h(n4)<LFS, LFS = 1.5*h(n4); end
% Loop through all WM in box and take min LFS
for i = start:nw % Loop through points (acending y-value order)
if (wm(i,2)<=y4) % Check box bounds and current min
if (wm(i,1)>=x1) && (wm(i,1)<=x2) && (L(i)<LFS)
LFS = L(i); % New min found - reset
end
else % Due to the sorting
break;
end
end
% Split box into 4
if (x2-x1)>=LFS
if (np+5)>=size(p,1) % Alloc memory on demand
p = [p; zeros(pblock,2)];
pblock = 2*pblock;
end
if (nb+3)>=size(b,1)
b = [b; zeros(bblock,4)];
test = [test; true(bblock,1)];
bblock = 2*bblock;
end
xm = x1+0.5*(x2-x1); % Current midpoints
ym = y1+0.5*(y4-y1);
% New nodes
p(np+1,1) = xm; p(np+1,2) = ym;
p(np+2,1) = xm; p(np+2,2) = y1;
p(np+3,1) = x2; p(np+3,2) = ym;
p(np+4,1) = xm; p(np+4,2) = y4;
p(np+5,1) = x1; p(np+5,2) = ym;
% New boxes
b(m,1) = n1; % Box 1
b(m,2) = np+2;
b(m,3) = np+1;
b(m,4) = np+5;
b(nb+1,1) = np+2; % Box 2
b(nb+1,2) = n2;
b(nb+1,3) = np+3;
b(nb+1,4) = np+1;
b(nb+2,1) = np+1; % Box 3
b(nb+2,2) = np+3;
b(nb+2,3) = n3;
b(nb+2,4) = np+4;
b(nb+3,1) = np+5; % Box 4
b(nb+3,2) = np+1;
b(nb+3,3) = np+4;
b(nb+3,4) = n4;
nb = nb+3;
np = np+5;
else
test(m) = false;
end
end
% User defined size function at new nodes
pr = rotate(p(N+1:np,:),-theta);
h = [h; userhfun(pr(:,1),pr(:,2),fun,args,hmax,XYmin,XYmax)];
end
p = p(1:np,:);
b = b(1:nb,:);
% Remove duplicate nodes
[p,i,j] = unique(p,'rows');
h = h(i);
b = reshape(j(b),size(b));
% FORM SIZE FUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
fprintf('Forming element size function\n');
end
% Unique edges
e = unique(sort([b(:,[1,2]); b(:,[2,3]); b(:,[3,4]); b(:,[4,1])],2),'rows');
L = sqrt(sum((p(e(:,1),:)-p(e(:,2),:)).^2,2)); % Edge length
ne = size(e,1);
for k = 1:ne % Initial h - minimum neighbouring edge length
Lk = L(k);
if Lk<h(e(k,1)), h(e(k,1)) = Lk; end
if Lk<h(e(k,2)), h(e(k,2)) = Lk; end
end
h = min(h,hmax);
% Gradient limiting
tol = 1.0e-06;
while true % Loop over the edges of the background mesh ensuring
h_old = h; % that dh satisfies the dhmax tolerance
for k = 1:ne % Loop over edges
n1 = e(k,1);
n2 = e(k,2);
Lk = L(k);
if h(n1)>h(n2) % Ensure grad(h)<=dhmax
dh = (h(n1)-h(n2))/Lk;
if dh>dhmax
h(n1) = h(n2) + dhmax*Lk;
end
else
dh = (h(n2)-h(n1))/Lk;
if dh>dhmax
h(n2) = h(n1) + dhmax*Lk;
end
end
end
if norm((h-h_old)./h,'inf')<tol % Test convergence
break
end
end
% TRIANGULATE QUADTREE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if output
fprintf('Triangulating quadtree\n');
end
if size(b,1)==1
% Split box diagonally into 2 tri's
t = [b(1),b(2),b(3); b(1),b(3),b(4)];
else
% Get node-to-node connectivity
% First column is column count per row
% Max neighbours is 8 due to quadtree setup
n2n = zeros(size(p,1),9);
for k = 1:size(e,1)
% Nodes in kth edge
n1 = e(k,1);
n2 = e(k,2);
% Indexing
n2n(n1,1) = n2n(n1,1)+1; % Node 1
n2n(n1,n2n(n1,1)+1) = n2;
n2n(n2,1) = n2n(n2,1)+1; % Node 2
n2n(n2,n2n(n2,1)+1) = n1;
end
% Check for regular boxes with no mid-side nodes
num = n2n(:,1)<=4;
reg = all(num(b),2);
% Split regular boxes diagonally into 2 tri's
t = [b(reg,[1,2,3]); b(reg,[1,3,4])];
if ~all(reg)
% Use the N2N connectivity to directly triangulate the quadtree
% nodes. Some additional nodes may be added at the centroids of some
% boxes to facilitate triangulation. The triangluation is not
% necessarily Delaunay, but should always be high quality and
% symmetric where possible.
b = b(~reg,:); % Boxes that still need to be dealt with
nb = size(b,1);
nt = size(t,1);
% Alloc space
t = [t; zeros(5*nb,3)]; % Has to be a least 5 times as many tri's as boxes
nlist = zeros(512,1); % Shouldn't ever be exceeded!
for k = 1:nb
% Corner nodes
n1 = b(k,1); n2 = b(k,2);
n3 = b(k,3); n4 = b(k,4);
% Assemble node list for kth box in CCW order
nlist(1) = n1;
count = 1;
next = 2;
while true
cn = nlist(next-1);
% Find the closest node to CN travelling CCW around box
old = inf;
for j = 1:n2n(cn,1)
nn = n2n(cn,j+1);
dx = p(nn,1)-p(cn,1);
dy = p(nn,2)-p(cn,2);
if count==1 % Edge 12
if (dx>0.0) && (dx<old)
old = dx;
tmp = nn;
end
elseif count==2 % Edge 23
if (dy>0.0) && (dy<old)
old = dy;
tmp = nn;
end
elseif count==3 % Edge 34
if (dx<0.0) && (abs(dx)<old)
old = abs(dx);
tmp = nn;
end
else % Edge 41
if (dy<0.0) && (abs(dy)<old)
old = abs(dy);
tmp = nn;
end
end
end
if tmp==n1 % Back to start - Done!
break
elseif (count<4) && (tmp==b(k,count+1)) % New edge
count = count+1;
end
nlist(next) = tmp;
next = next+1;
end
nnode = next-1;
if (nt+nnode)>=size(t,1) % Realloc memory on demand
t = [t; zeros(nb,3)];
end
if (np+1)>=size(p,1)
p = [p; zeros(nb,2)];
h = [h; zeros(nb,1)];
end
% Triangulate box
if nnode==4 % Special treatment if no mid-side nodes
% Split box diagonally into 2 tri's
% New tri's
t(nt+1,1) = n1; % t1
t(nt+1,2) = n2;
t(nt+1,3) = n3;
t(nt+2,1) = n1; % t2
t(nt+2,2) = n3;
t(nt+2,3) = n4;
% Update count
nt = nt+2;
elseif nnode==5 % Special treatment if only 1 mid-side node
% Split box into 3 tri's centred at mid-side node
% Find the mid-side node
j = 2;
while j<=4
if nlist(j)~=b(k,j)
break
end
j = j+1;
end
% Permute indexing so that the split is always between n1,n2
if j==3
n1 = b(k,2); n2 = b(k,3);
n3 = b(k,4); n4 = b(k,1);
elseif j==4
n1 = b(k,3); n2 = b(k,4);
n3 = b(k,1); n4 = b(k,2);
elseif j==5
n1 = b(k,4); n2 = b(k,1);
n3 = b(k,2); n4 = b(k,3);
end
% New tri's
t(nt+1,1) = n1; % t1
t(nt+1,2) = nlist(j);
t(nt+1,3) = n4;
t(nt+2,1) = nlist(j); % t2
t(nt+2,2) = n2;
t(nt+2,3) = n3;
t(nt+3,1) = n4; % t3
t(nt+3,2) = nlist(j);
t(nt+3,3) = n3;
% Update count
nt = nt+3;
else % Connect all mid-side nodes to an additional node
% introduced at the centroid
% New tri's
xave = 0.0;
yave = 0.0;
have = 0.0;
for j = 1:nnode-1
jj = nlist(j);
% New tri's
t(nt+j,1) = jj;
t(nt+j,2) = np+1;
t(nt+j,3) = nlist(j+1);
% Averaging
xave = xave+p(jj,1);
yave = yave+p(jj,2);
have = have+h(jj);
end
jj = nlist(nnode);
% Last tri
t(nt+nnode,1) = jj;
t(nt+nnode,2) = np+1;
t(nt+nnode,3) = nlist(1);
% New node
p(np+1,1) = (xave+p(jj,1)) /nnode;
p(np+1,2) = (yave+p(jj,2)) /nnode;
h(np+1) = (have+h(jj)) /nnode;
% Update count
nt = nt+nnode;
np = np+1;
end
end
p = p(1:np,:);
h = h(1:np);
t = t(1:nt,:);
end
end
% Undo rotation
p = rotate(p,-theta);
end % quadtree()
%% SUB-FUNCTIONS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function theta = minrectangle(p)
% Find the rotation angle that must be applied to the 2D points in P so
% that the long axis of the minimum bounding rectangle is aligned with the
% Y axis.
n = size(p,1);
if numel(p)~=2*n
error('P must be an Nx2 array');
end
if n>2
% Convex hull edge segments
e = convhulln(p);
% Keep convex points
i = unique(e(:));
p = p(i,:);
% Re-index to keep E consistent
j = zeros(size(p,1),1);
j(i) = 1;
j = cumsum(j);
e = j(e);
% Angles of hull segments
dxy = p(e(:,2),:)-p(e(:,1),:);
ang = atan2(dxy(:,2),dxy(:,1));
% Check all hull edge segments
Aold = inf;
for k = 1:size(e,1)
% Rotate through -ang(k)
pr = rotate(p,-ang(k));
% Compute area of bounding rectangle and save if better
dxy = max(pr,[],1)-min(pr,[],1);
A = dxy(1)*dxy(2);
if A<Aold
Aold = A;
theta = -ang(k);
end
end
% Check result to ensure that the long axis is aligned with Y
pr = rotate(p,theta);
dxy = max(pr,[],1)-min(pr,[],1);
if dxy(1)>dxy(2)
% Need to flip XY
theta = theta+0.5*pi;
end
else
% 1 or 2 points, degenerate bounding rectangle in either case
theta = 0.0;
end
end % minrectangle()
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function p = rotate(p,theta)
% Rotate the 2D points in P through the angle THETA (radians).
stheta = sin(theta);
ctheta = cos(theta);
p = p*[ctheta, stheta; -stheta, ctheta];
end % rotate()
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function h = userhfun(x,y,fun,args,hmax,xymin,xymax)
% Evaluate user defined size function.
if ~isempty(fun)
h = feval(fun,x,y,args{:});
if size(h)~=size(x)
error('Incorrect user defined size function. SIZE(H) must equal SIZE(X).');
end
else
h = inf*ones(size(x));
end
h = min(h,hmax);
% Limit to domain
out = (x>xymax(1))|(x<xymin(1))|(y>xymax(2))|(y<xymin(2));
h(out) = inf;
if any(h<=0.0)
error('Incorrect user defined size function. H must be positive.');
end
end % userhfun()
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [hmax,edgeh,fun,args] = gethdata(hdata)
% Check the user defined size functions
d_hmax = inf;
d_edgeh = [];
d_fun = '';
d_args = {};
if ~isempty(hdata)
if ~isstruct(hdata)
error('HDATA must be a structure');
end
if numel(hdata)~=1
error('HDATA cannot be an array of structures');
end
fields = fieldnames(hdata);
names = {'hmax','edgeh','fun','args'};
for k = 1:length(fields)
if ~any(strcmp(fields{k},names))
error('Invalid field in HDATA');
end
end
if isfield(hdata,'hmax')
if (numel(hdata.hmax)~=1) || (hdata.hmax<=0)
error('HDATA.HMAX must be a positive scalar');
else
hmax = hdata.hmax;
end
else
hmax = d_hmax;
end
if isfield(hdata,'edgeh')
if (numel(hdata.edgeh)~=2*size(hdata.edgeh,1)) || any(hdata.edgeh(:)<0)
error('HDATA.EDGEH must be a positive Kx2 array');
else
edgeh = hdata.edgeh;
end
else
edgeh = d_edgeh;
end
if isfield(hdata,'fun')
if ~ischar(hdata.fun) && ~isa(hdata.fun,'function_handle')
error('HDATA.FUN must be a function name or a function handle');
else
fun = hdata.fun;
end
else
fun = d_fun;
end
if isfield(hdata,'args')
if ~iscell(hdata.args)
error(['HDATA.ARGS must be a cell array of additional' ...
'inputs for HDATA.FUN']);
else
args = hdata.args;
end
else
args = d_args;
end
else
hmax = d_hmax;
edgeh = d_edgeh;
fun = d_fun;
args = d_args;
end
end % gethdata()