1
15
16
20
21 #include "moab/BSPTree.hpp"
22 #include "moab/GeomUtil.hpp"
23 #include "moab/Range.hpp"
24 #include "Internals.hpp"
25 #include "moab/BSPTreePoly.hpp"
26 #include "moab/Util.hpp"
27
28 #include <cassert>
29 #include <cstring>
30 #include <algorithm>
31 #include <limits>
32
33 #if defined( MOAB_HAVE_IEEEFP_H )
34 #include <ieeefp.h>
35 #endif
36
37 #define MB_BSP_TREE_DEFAULT_TAG_NAME "BSPTree"
38
39 namespace moab
40 {
41
42 static void corners_from_box( const double box_min[3], const double box_max[3], double corners[8][3] )
43 {
44 const double* ranges[] = { box_min, box_max };
45 for( int z = 0; z < 2; ++z )
46 {
47 corners[4 * z][0] = box_min[0];
48 corners[4 * z][1] = box_min[1];
49 corners[4 * z][2] = ranges[z][2];
50
51 corners[4 * z + 1][0] = box_max[0];
52 corners[4 * z + 1][1] = box_min[1];
53 corners[4 * z + 1][2] = ranges[z][2];
54
55 corners[4 * z + 2][0] = box_max[0];
56 corners[4 * z + 2][1] = box_max[1];
57 corners[4 * z + 2][2] = ranges[z][2];
58
59 corners[4 * z + 3][0] = box_min[0];
60 corners[4 * z + 3][1] = box_max[1];
61 corners[4 * z + 3][2] = ranges[z][2];
62 }
63 }
64
65
66
67 static bool point_in_box( const double corners[8][3], const double point[3] )
68 {
69 const unsigned side_verts[6][3] = { { 0, 3, 1 }, { 4, 5, 7 }, { 0, 1, 4 }, { 1, 2, 5 }, { 2, 3, 6 }, { 3, 0, 7 } };
70
71
72 const CartVect pt( point );
73 for( unsigned s = 0; s < 6; ++s )
74 {
75 CartVect v0( corners[side_verts[s][0]] );
76 CartVect v1( corners[side_verts[s][1]] );
77 CartVect v2( corners[side_verts[s][2]] );
78 CartVect N = ( v1 - v0 ) * ( v2 - v0 );
79 if( ( v0 - pt ) % N < 0 ) return false;
80 }
81 return true;
82 }
83
84 void BSPTree::Plane::set( const double pt1[3], const double pt2[3], const double pt3[3] )
85 {
86 const double v1[] = { pt2[0] - pt1[0], pt2[1] - pt1[1], pt2[2] - pt1[2] };
87 const double v2[] = { pt3[0] - pt1[0], pt3[1] - pt1[1], pt3[2] - pt1[2] };
88 const double nrm[] = { v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2],
89 v1[0] * v2[1] - v1[1] * v2[0] };
90 set( nrm, pt1 );
91 }
92
93 ErrorCode BSPTree::init_tags( const char* tagname )
94 {
95 if( !tagname ) tagname = MB_BSP_TREE_DEFAULT_TAG_NAME;
96
97 std::string rootname( tagname );
98 rootname += "_box";
99
100 ErrorCode rval = moab()->tag_get_handle( tagname, 4, MB_TYPE_DOUBLE, planeTag, MB_TAG_CREAT | MB_TAG_DENSE );
101 if( MB_SUCCESS != rval )
102 planeTag = 0;
103 else
104 rval = moab()->tag_get_handle( rootname.c_str(), 24, MB_TYPE_DOUBLE, rootTag, MB_TAG_CREAT | MB_TAG_SPARSE );
105 if( MB_SUCCESS != rval ) rootTag = 0;
106 return rval;
107 }
108
109 BSPTree::BSPTree( Interface* mb, const char* tagname, unsigned set_flags )
110 : mbInstance( mb ), meshSetFlags( set_flags ), cleanUpTrees( false )
111 {
112 init_tags( tagname );
113 }
114
115 BSPTree::BSPTree( Interface* mb, bool destroy_created_trees, const char* tagname, unsigned set_flags )
116 : mbInstance( mb ), meshSetFlags( set_flags ), cleanUpTrees( destroy_created_trees )
117 {
118 init_tags( tagname );
119 }
120
121 BSPTree::~BSPTree()
122 {
123 if( !cleanUpTrees ) return;
124
125 while( !createdTrees.empty() )
126 {
127 EntityHandle tree = createdTrees.back();
128
129 const void* data_ptr = 0;
130 ErrorCode rval = moab()->tag_get_by_ptr( rootTag, &tree, 1, &data_ptr );
131 if( MB_SUCCESS == rval ) rval = delete_tree( tree );
132 if( MB_SUCCESS != rval ) createdTrees.pop_back();
133 }
134 }
135
136 ErrorCode BSPTree::set_split_plane( EntityHandle node, const Plane& p )
137 {
138
139 const double lensqr = p.norm[0] * p.norm[0] + p.norm[1] * p.norm[1] + p.norm[2] * p.norm[2];
140 if( fabs( lensqr - 1.0 ) < std::numeric_limits< double >::epsilon() )
141 return moab()->tag_set_data( planeTag, &node, 1, &p );
142
143 const double inv_len = 1.0 / sqrt( lensqr );
144 Plane p2( p );
145 p2.norm[0] *= inv_len;
146 p2.norm[1] *= inv_len;
147 p2.norm[2] *= inv_len;
148 p2.coeff *= inv_len;
149
150
151 if( !Util::is_finite( p2.norm[0] + p2.norm[1] + p2.norm[2] + p2.coeff ) ) return MB_FAILURE;
152
153
154 return moab()->tag_set_data( planeTag, &node, 1, &p2 );
155 }
156
157 ErrorCode BSPTree::set_tree_box( EntityHandle root_handle, const double box_min[3], const double box_max[3] )
158 {
159 double corners[8][3];
160 corners_from_box( box_min, box_max, corners );
161 return set_tree_box( root_handle, corners );
162 }
163
164 ErrorCode BSPTree::set_tree_box( EntityHandle root_handle, const double corners[8][3] )
165 {
166 return moab()->tag_set_data( rootTag, &root_handle, 1, corners );
167 }
168
169 ErrorCode BSPTree::get_tree_box( EntityHandle root_handle, double corners[8][3] )
170 {
171 return moab()->tag_get_data( rootTag, &root_handle, 1, corners );
172 }
173
174 ErrorCode BSPTree::get_tree_box( EntityHandle root_handle, double corners[24] )
175 {
176 return moab()->tag_get_data( rootTag, &root_handle, 1, corners );
177 }
178
179 ErrorCode BSPTree::create_tree( EntityHandle& root_handle )
180 {
181 const double min[3] = { -HUGE_VAL, -HUGE_VAL, -HUGE_VAL };
182 const double max[3] = { HUGE_VAL, HUGE_VAL, HUGE_VAL };
183 return create_tree( min, max, root_handle );
184 }
185
186 ErrorCode BSPTree::create_tree( const double corners[8][3], EntityHandle& root_handle )
187 {
188 ErrorCode rval = moab()->create_meshset( meshSetFlags, root_handle );
189 if( MB_SUCCESS != rval ) return rval;
190
191 rval = set_tree_box( root_handle, corners );
192 if( MB_SUCCESS != rval )
193 {
194 moab()->delete_entities( &root_handle, 1 );
195 root_handle = 0;
196 return rval;
197 }
198
199 createdTrees.push_back( root_handle );
200 return MB_SUCCESS;
201 }
202
203 ErrorCode BSPTree::create_tree( const double box_min[3], const double box_max[3], EntityHandle& root_handle )
204 {
205 double corners[8][3];
206 corners_from_box( box_min, box_max, corners );
207 return create_tree( corners, root_handle );
208 }
209
210 ErrorCode BSPTree::delete_tree( EntityHandle root_handle )
211 {
212 ErrorCode rval;
213
214 std::vector< EntityHandle > children, dead_sets, current_sets;
215 current_sets.push_back( root_handle );
216 while( !current_sets.empty() )
217 {
218 EntityHandle set = current_sets.back();
219 current_sets.pop_back();
220 dead_sets.push_back( set );
221 rval = moab()->get_child_meshsets( set, children );
222 if( MB_SUCCESS != rval ) return rval;
223 std::copy( children.begin(), children.end(), std::back_inserter( current_sets ) );
224 children.clear();
225 }
226
227 rval = moab()->tag_delete_data( rootTag, &root_handle, 1 );
228 if( MB_SUCCESS != rval ) return rval;
229
230 createdTrees.erase( std::remove( createdTrees.begin(), createdTrees.end(), root_handle ), createdTrees.end() );
231 return moab()->delete_entities( &dead_sets[0], dead_sets.size() );
232 }
233
234 ErrorCode BSPTree::find_all_trees( Range& results )
235 {
236 return moab()->get_entities_by_type_and_tag( 0, MBENTITYSET, &rootTag, 0, 1, results );
237 }
238
239 ErrorCode BSPTree::get_tree_iterator( EntityHandle root, BSPTreeIter& iter )
240 {
241 ErrorCode rval = iter.initialize( this, root );
242 if( MB_SUCCESS != rval ) return rval;
243 return iter.step_to_first_leaf( BSPTreeIter::LEFT );
244 }
245
246 ErrorCode BSPTree::get_tree_end_iterator( EntityHandle root, BSPTreeIter& iter )
247 {
248 ErrorCode rval = iter.initialize( this, root );
249 if( MB_SUCCESS != rval ) return rval;
250 return iter.step_to_first_leaf( BSPTreeIter::RIGHT );
251 }
252
253 ErrorCode BSPTree::split_leaf( BSPTreeIter& leaf, Plane plane, EntityHandle& left, EntityHandle& right )
254 {
255 ErrorCode rval;
256
257 rval = moab()->create_meshset( meshSetFlags, left );
258 if( MB_SUCCESS != rval ) return rval;
259
260 rval = moab()->create_meshset( meshSetFlags, right );
261 if( MB_SUCCESS != rval )
262 {
263 moab()->delete_entities( &left, 1 );
264 return rval;
265 }
266
267 if( MB_SUCCESS != set_split_plane( leaf.handle(), plane ) ||
268 MB_SUCCESS != moab()->add_child_meshset( leaf.handle(), left ) ||
269 MB_SUCCESS != moab()->add_child_meshset( leaf.handle(), right ) ||
270 MB_SUCCESS != leaf.step_to_first_leaf( BSPTreeIter::LEFT ) )
271 {
272 EntityHandle children[] = { left, right };
273 moab()->delete_entities( children, 2 );
274 return MB_FAILURE;
275 }
276
277 return MB_SUCCESS;
278 }
279
280 ErrorCode BSPTree::split_leaf( BSPTreeIter& leaf, Plane plane )
281 {
282 EntityHandle left, right;
283 return split_leaf( leaf, plane, left, right );
284 }
285
286 ErrorCode BSPTree::split_leaf( BSPTreeIter& leaf, Plane plane, const Range& left_entities, const Range& right_entities )
287 {
288 EntityHandle left, right, parent = leaf.handle();
289 ErrorCode rval = split_leaf( leaf, plane, left, right );
290 if( MB_SUCCESS != rval ) return rval;
291
292 if( MB_SUCCESS == moab()->add_entities( left, left_entities ) &&
293 MB_SUCCESS == moab()->add_entities( right, right_entities ) &&
294 MB_SUCCESS == moab()->clear_meshset( &parent, 1 ) )
295 return MB_SUCCESS;
296
297 moab()->remove_child_meshset( parent, left );
298 moab()->remove_child_meshset( parent, right );
299 EntityHandle children[] = { left, right };
300 moab()->delete_entities( children, 2 );
301 return MB_FAILURE;
302 }
303
304 ErrorCode BSPTree::split_leaf( BSPTreeIter& leaf,
305 Plane plane,
306 const std::vector< EntityHandle >& left_entities,
307 const std::vector< EntityHandle >& right_entities )
308 {
309 EntityHandle left, right, parent = leaf.handle();
310 ErrorCode rval = split_leaf( leaf, plane, left, right );
311 if( MB_SUCCESS != rval ) return rval;
312
313 if( MB_SUCCESS == moab()->add_entities( left, &left_entities[0], left_entities.size() ) &&
314 MB_SUCCESS == moab()->add_entities( right, &right_entities[0], right_entities.size() ) &&
315 MB_SUCCESS == moab()->clear_meshset( &parent, 1 ) )
316 return MB_SUCCESS;
317
318 moab()->remove_child_meshset( parent, left );
319 moab()->remove_child_meshset( parent, right );
320 EntityHandle children[] = { left, right };
321 moab()->delete_entities( children, 2 );
322 return MB_FAILURE;
323 }
324
325 ErrorCode BSPTree::merge_leaf( BSPTreeIter& iter )
326 {
327 ErrorCode rval;
328 if( iter.depth() == 1 )
329 return MB_FAILURE;
330
331
332 iter.up();
333
334
335 EntityHandle parent = iter.handle();
336 iter.childVect.clear();
337 rval = moab()->get_child_meshsets( parent, iter.childVect );
338 if( MB_SUCCESS != rval ) return rval;
339
340
341 moab()->remove_child_meshset( parent, iter.childVect[0] );
342 moab()->remove_child_meshset( parent, iter.childVect[1] );
343 std::vector< EntityHandle > stack( iter.childVect );
344
345
346 Range range;
347 while( !stack.empty() )
348 {
349 EntityHandle h = stack.back();
350 stack.pop_back();
351 range.clear();
352 rval = moab()->get_entities_by_handle( h, range );
353 if( MB_SUCCESS != rval ) return rval;
354 rval = moab()->add_entities( parent, range );
355 if( MB_SUCCESS != rval ) return rval;
356
357 iter.childVect.clear();
358 rval = moab()->get_child_meshsets( h, iter.childVect );MB_CHK_ERR( rval );
359 if( !iter.childVect.empty() )
360 {
361 moab()->remove_child_meshset( h, iter.childVect[0] );
362 moab()->remove_child_meshset( h, iter.childVect[1] );
363 stack.push_back( iter.childVect[0] );
364 stack.push_back( iter.childVect[1] );
365 }
366
367 rval = moab()->delete_entities( &h, 1 );
368 if( MB_SUCCESS != rval ) return rval;
369 }
370
371 return MB_SUCCESS;
372 }
373
374 ErrorCode BSPTreeIter::initialize( BSPTree* btool, EntityHandle root, const double* )
375 {
376 treeTool = btool;
377 mStack.clear();
378 mStack.push_back( root );
379 return MB_SUCCESS;
380 }
381
382 ErrorCode BSPTreeIter::step_to_first_leaf( Direction direction )
383 {
384 ErrorCode rval;
385 for( ;; )
386 {
387 childVect.clear();
388 rval = tool()->moab()->get_child_meshsets( mStack.back(), childVect );
389 if( MB_SUCCESS != rval ) return rval;
390 if( childVect.empty() )
391 break;
392
393 mStack.push_back( childVect[direction] );
394 }
395 return MB_SUCCESS;
396 }
397
398 ErrorCode BSPTreeIter::step( Direction direction )
399 {
400 EntityHandle node, parent;
401 ErrorCode rval;
402 const Direction opposite = static_cast< Direction >( 1 - direction );
403
404
405
406
407 if( mStack.empty() ) return MB_FAILURE;
408
409
410
411
412 node = mStack.back();
413 mStack.pop_back();
414
415 while( !mStack.empty() )
416 {
417
418 parent = mStack.back();
419 childVect.clear();
420 rval = tool()->moab()->get_child_meshsets( parent, childVect );
421 if( MB_SUCCESS != rval ) return rval;
422
423
424 if( childVect[opposite] == node )
425 {
426
427 mStack.push_back( childVect[direction] );
428
429 return step_to_first_leaf( opposite );
430 }
431
432
433
434 assert( childVect[direction] == node );
435 node = parent;
436 mStack.pop_back();
437 }
438
439 return MB_ENTITY_NOT_FOUND;
440 }
441
442 ErrorCode BSPTreeIter::up()
443 {
444 if( mStack.size() < 2 ) return MB_ENTITY_NOT_FOUND;
445 mStack.pop_back();
446 return MB_SUCCESS;
447 }
448
449 ErrorCode BSPTreeIter::down( const BSPTree::Plane& , Direction dir )
450 {
451 childVect.clear();
452 ErrorCode rval = tool()->moab()->get_child_meshsets( mStack.back(), childVect );
453 if( MB_SUCCESS != rval ) return rval;
454 if( childVect.empty() ) return MB_ENTITY_NOT_FOUND;
455
456 mStack.push_back( childVect[dir] );
457 return MB_SUCCESS;
458 }
459
460 ErrorCode BSPTreeIter::get_parent_split_plane( BSPTree::Plane& plane ) const
461 {
462 if( mStack.size() < 2 )
463 return MB_ENTITY_NOT_FOUND;
464
465 EntityHandle parent = mStack[mStack.size() - 2];
466 return tool()->get_split_plane( parent, plane );
467 }
468
469 double BSPTreeIter::volume() const
470 {
471 BSPTreePoly polyhedron;
472 ErrorCode rval = calculate_polyhedron( polyhedron );
473 return MB_SUCCESS == rval ? polyhedron.volume() : -1.0;
474 }
475
476 bool BSPTreeIter::is_sibling( const BSPTreeIter& other_leaf ) const
477 {
478 const size_t s = mStack.size();
479 return ( s > 1 ) && ( s == other_leaf.mStack.size() ) && ( other_leaf.mStack[s - 2] == mStack[s - 2] ) &&
480 other_leaf.handle() != handle();
481 }
482
483 bool BSPTreeIter::is_sibling( EntityHandle other_leaf ) const
484 {
485 if( mStack.size() < 2 || other_leaf == handle() ) return false;
486 EntityHandle parent = mStack[mStack.size() - 2];
487 childVect.clear();
488 ErrorCode rval = tool()->moab()->get_child_meshsets( parent, childVect );
489 if( MB_SUCCESS != rval || childVect.size() != 2 )
490 {
491 assert( false );
492 return false;
493 }
494 return childVect[0] == other_leaf || childVect[1] == other_leaf;
495 }
496
497 bool BSPTreeIter::sibling_is_forward() const
498 {
499 if( mStack.size() < 2 )
500 return false;
501 EntityHandle parent = mStack[mStack.size() - 2];
502 childVect.clear();
503 ErrorCode rval = tool()->moab()->get_child_meshsets( parent, childVect );
504 if( MB_SUCCESS != rval || childVect.size() != 2 )
505 {
506 assert( false );
507 return false;
508 }
509 return childVect[0] == handle();
510 }
511
512 ErrorCode BSPTreeIter::calculate_polyhedron( BSPTreePoly& poly_out ) const
513 {
514 ErrorCode rval;
515
516 assert( sizeof( CartVect ) == 3 * sizeof( double ) );
517 CartVect corners[8];
518 rval = treeTool->get_tree_box( mStack.front(), corners[0].array() );
519 if( MB_SUCCESS != rval ) return rval;
520
521 rval = poly_out.set( corners );
522 if( MB_SUCCESS != rval ) return rval;
523
524 BSPTree::Plane plane;
525 std::vector< EntityHandle >::const_iterator i = mStack.begin();
526 std::vector< EntityHandle >::const_iterator here = mStack.end() - 1;
527 while( i != here )
528 {
529 rval = treeTool->get_split_plane( *i, plane );
530 if( MB_SUCCESS != rval ) return rval;
531
532 childVect.clear();
533 rval = treeTool->moab()->get_child_meshsets( *i, childVect );
534 if( MB_SUCCESS != rval ) return rval;
535 if( childVect.size() != 2 ) return MB_FAILURE;
536
537 ++i;
538 if( childVect[1] == *i ) plane.flip();
539
540 CartVect norm( plane.norm );
541 poly_out.cut_polyhedron( norm, plane.coeff );
542 }
543
544 return MB_SUCCESS;
545 }
546
547 ErrorCode BSPTreeBoxIter::initialize( BSPTree* tool_ptr, EntityHandle root, const double* point )
548 {
549 ErrorCode rval = BSPTreeIter::initialize( tool_ptr, root );
550 if( MB_SUCCESS != rval ) return rval;
551
552 rval = tool()->get_tree_box( root, leafCoords );
553 if( MB_SUCCESS != rval ) return rval;
554
555 if( point && !point_in_box( leafCoords, point ) ) return MB_ENTITY_NOT_FOUND;
556
557 stackData.resize( 1 );
558 return MB_SUCCESS;
559 }
560
561 BSPTreeBoxIter::SideBits BSPTreeBoxIter::side_above_plane( const double hex_coords[8][3], const BSPTree::Plane& plane )
562 {
563 unsigned result = 0;
564 for( unsigned i = 0; i < 8u; ++i )
565 result |= plane.above( hex_coords[i] ) << i;
566 return (BSPTreeBoxIter::SideBits)result;
567 }
568
569 BSPTreeBoxIter::SideBits BSPTreeBoxIter::side_on_plane( const double hex_coords[8][3], const BSPTree::Plane& plane )
570 {
571 unsigned result = 0;
572 for( unsigned i = 0; i < 8u; ++i )
573 {
574 bool on = plane.distance( hex_coords[i] ) <= BSPTree::epsilon();
575 result |= on << i;
576 }
577 return (BSPTreeBoxIter::SideBits)result;
578 }
579
580 static inline void copy_coords( const double src[3], double dest[3] )
581 {
582 dest[0] = src[0];
583 dest[1] = src[1];
584 dest[2] = src[2];
585 }
586
587 ErrorCode BSPTreeBoxIter::face_corners( const SideBits face, const double hex_corners[8][3], double face_corners[4][3] )
588 {
589 switch( face )
590 {
591 case BSPTreeBoxIter::B0154:
592 copy_coords( hex_corners[0], face_corners[0] );
593 copy_coords( hex_corners[1], face_corners[1] );
594 copy_coords( hex_corners[5], face_corners[2] );
595 copy_coords( hex_corners[4], face_corners[3] );
596 break;
597 case BSPTreeBoxIter::B1265:
598 copy_coords( hex_corners[1], face_corners[0] );
599 copy_coords( hex_corners[2], face_corners[1] );
600 copy_coords( hex_corners[6], face_corners[2] );
601 copy_coords( hex_corners[5], face_corners[3] );
602 break;
603 case BSPTreeBoxIter::B2376:
604 copy_coords( hex_corners[2], face_corners[0] );
605 copy_coords( hex_corners[3], face_corners[1] );
606 copy_coords( hex_corners[7], face_corners[2] );
607 copy_coords( hex_corners[6], face_corners[3] );
608 break;
609 case BSPTreeBoxIter::B3047:
610 copy_coords( hex_corners[3], face_corners[0] );
611 copy_coords( hex_corners[0], face_corners[1] );
612 copy_coords( hex_corners[4], face_corners[2] );
613 copy_coords( hex_corners[7], face_corners[3] );
614 break;
615 case BSPTreeBoxIter::B3210:
616 copy_coords( hex_corners[3], face_corners[0] );
617 copy_coords( hex_corners[2], face_corners[1] );
618 copy_coords( hex_corners[1], face_corners[2] );
619 copy_coords( hex_corners[0], face_corners[3] );
620 break;
621 case BSPTreeBoxIter::B4567:
622 copy_coords( hex_corners[4], face_corners[0] );
623 copy_coords( hex_corners[5], face_corners[1] );
624 copy_coords( hex_corners[6], face_corners[2] );
625 copy_coords( hex_corners[7], face_corners[3] );
626 break;
627 default:
628 return MB_FAILURE;
629 }
630
631 return MB_SUCCESS;
632 }
633
634
641 static inline void plane_cut_edge( double old_coords_out[3],
642 const double keep_end_coords[3],
643 double cut_end_coords[3],
644 const BSPTree::Plane& plane )
645 {
646 const CartVect start( keep_end_coords ), end( cut_end_coords );
647 const CartVect norm( plane.norm );
648 CartVect xsect_point;
649
650 const CartVect m = end - start;
651 const double t = -( norm % start + plane.coeff ) / ( norm % m );
652 assert( t > 0.0 && t < 1.0 );
653 xsect_point = start + t * m;
654
655 end.get( old_coords_out );
656 xsect_point.get( cut_end_coords );
657 }
658
659
667 static ErrorCode plane_cut_box( double cut_face_out[4][3], double corners_inout[8][3], const BSPTree::Plane& plane )
668 {
669 switch( BSPTreeBoxIter::side_above_plane( corners_inout, plane ) )
670 {
671 case BSPTreeBoxIter::B0154:
672 plane_cut_edge( cut_face_out[0], corners_inout[3], corners_inout[0], plane );
673 plane_cut_edge( cut_face_out[1], corners_inout[2], corners_inout[1], plane );
674 plane_cut_edge( cut_face_out[2], corners_inout[6], corners_inout[5], plane );
675 plane_cut_edge( cut_face_out[3], corners_inout[7], corners_inout[4], plane );
676 break;
677 case BSPTreeBoxIter::B1265:
678 plane_cut_edge( cut_face_out[0], corners_inout[0], corners_inout[1], plane );
679 plane_cut_edge( cut_face_out[1], corners_inout[3], corners_inout[2], plane );
680 plane_cut_edge( cut_face_out[2], corners_inout[7], corners_inout[6], plane );
681 plane_cut_edge( cut_face_out[3], corners_inout[4], corners_inout[5], plane );
682 break;
683 case BSPTreeBoxIter::B2376:
684 plane_cut_edge( cut_face_out[0], corners_inout[1], corners_inout[2], plane );
685 plane_cut_edge( cut_face_out[1], corners_inout[0], corners_inout[3], plane );
686 plane_cut_edge( cut_face_out[2], corners_inout[4], corners_inout[7], plane );
687 plane_cut_edge( cut_face_out[3], corners_inout[5], corners_inout[6], plane );
688 break;
689 case BSPTreeBoxIter::B3047:
690 plane_cut_edge( cut_face_out[0], corners_inout[2], corners_inout[3], plane );
691 plane_cut_edge( cut_face_out[1], corners_inout[1], corners_inout[0], plane );
692 plane_cut_edge( cut_face_out[2], corners_inout[5], corners_inout[4], plane );
693 plane_cut_edge( cut_face_out[3], corners_inout[6], corners_inout[7], plane );
694 break;
695 case BSPTreeBoxIter::B3210:
696 plane_cut_edge( cut_face_out[0], corners_inout[7], corners_inout[3], plane );
697 plane_cut_edge( cut_face_out[1], corners_inout[6], corners_inout[2], plane );
698 plane_cut_edge( cut_face_out[2], corners_inout[5], corners_inout[1], plane );
699 plane_cut_edge( cut_face_out[3], corners_inout[4], corners_inout[0], plane );
700 break;
701 case BSPTreeBoxIter::B4567:
702 plane_cut_edge( cut_face_out[0], corners_inout[0], corners_inout[4], plane );
703 plane_cut_edge( cut_face_out[1], corners_inout[1], corners_inout[5], plane );
704 plane_cut_edge( cut_face_out[2], corners_inout[2], corners_inout[6], plane );
705 plane_cut_edge( cut_face_out[3], corners_inout[3], corners_inout[7], plane );
706 break;
707 default:
708 return MB_FAILURE;
709 }
710
711 return MB_SUCCESS;
712 }
713
714 static inline void copy_coords( double dest[3], const double source[3] )
715 {
716 dest[0] = source[0];
717 dest[1] = source[1];
718 dest[2] = source[2];
719 }
720
721
722 static inline ErrorCode plane_uncut_box( const double cut_face_in[4][3],
723 double corners_inout[8][3],
724 const BSPTree::Plane& plane )
725 {
726 switch( BSPTreeBoxIter::side_on_plane( corners_inout, plane ) )
727 {
728 case BSPTreeBoxIter::B0154:
729 copy_coords( corners_inout[0], cut_face_in[0] );
730 copy_coords( corners_inout[1], cut_face_in[1] );
731 copy_coords( corners_inout[5], cut_face_in[2] );
732 copy_coords( corners_inout[4], cut_face_in[3] );
733 break;
734 case BSPTreeBoxIter::B1265:
735 copy_coords( corners_inout[1], cut_face_in[0] );
736 copy_coords( corners_inout[2], cut_face_in[1] );
737 copy_coords( corners_inout[6], cut_face_in[2] );
738 copy_coords( corners_inout[5], cut_face_in[3] );
739 break;
740 case BSPTreeBoxIter::B2376:
741 copy_coords( corners_inout[2], cut_face_in[0] );
742 copy_coords( corners_inout[3], cut_face_in[1] );
743 copy_coords( corners_inout[7], cut_face_in[2] );
744 copy_coords( corners_inout[6], cut_face_in[3] );
745 break;
746 case BSPTreeBoxIter::B3047:
747 copy_coords( corners_inout[3], cut_face_in[0] );
748 copy_coords( corners_inout[0], cut_face_in[1] );
749 copy_coords( corners_inout[4], cut_face_in[2] );
750 copy_coords( corners_inout[7], cut_face_in[3] );
751 break;
752 case BSPTreeBoxIter::B3210:
753 copy_coords( corners_inout[3], cut_face_in[0] );
754 copy_coords( corners_inout[2], cut_face_in[1] );
755 copy_coords( corners_inout[1], cut_face_in[2] );
756 copy_coords( corners_inout[0], cut_face_in[3] );
757 break;
758 case BSPTreeBoxIter::B4567:
759 copy_coords( corners_inout[4], cut_face_in[0] );
760 copy_coords( corners_inout[5], cut_face_in[1] );
761 copy_coords( corners_inout[6], cut_face_in[2] );
762 copy_coords( corners_inout[7], cut_face_in[3] );
763 break;
764 default:
765 return MB_FAILURE;
766 }
767
768 return MB_SUCCESS;
769 }
770
771 ErrorCode BSPTreeBoxIter::step_to_first_leaf( Direction direction )
772 {
773 ErrorCode rval;
774 BSPTree::Plane plane;
775 Corners clipped_corners;
776
777 for( ;; )
778 {
779 childVect.clear();
780 rval = tool()->moab()->get_child_meshsets( mStack.back(), childVect );
781 if( MB_SUCCESS != rval ) return rval;
782 if( childVect.empty() )
783 break;
784
785 rval = tool()->get_split_plane( mStack.back(), plane );
786 if( MB_SUCCESS != rval ) return rval;
787
788 if( direction == RIGHT ) plane.flip();
789 rval = plane_cut_box( clipped_corners.coords, leafCoords, plane );
790 if( MB_SUCCESS != rval ) return rval;
791 mStack.push_back( childVect[direction] );
792 stackData.push_back( clipped_corners );
793 }
794 return MB_SUCCESS;
795 }
796
797 ErrorCode BSPTreeBoxIter::up()
798 {
799 ErrorCode rval;
800 if( mStack.size() == 1 ) return MB_ENTITY_NOT_FOUND;
801
802 EntityHandle node = mStack.back();
803 Corners clipped_face = stackData.back();
804 mStack.pop_back();
805 stackData.pop_back();
806
807 BSPTree::Plane plane;
808 rval = tool()->get_split_plane( mStack.back(), plane );
809 if( MB_SUCCESS != rval )
810 {
811 mStack.push_back( node );
812 stackData.push_back( clipped_face );
813 return rval;
814 }
815
816 rval = plane_uncut_box( clipped_face.coords, leafCoords, plane );
817 if( MB_SUCCESS != rval )
818 {
819 mStack.push_back( node );
820 stackData.push_back( clipped_face );
821 return rval;
822 }
823
824 return MB_SUCCESS;
825 }
826
827 ErrorCode BSPTreeBoxIter::down( const BSPTree::Plane& plane_ref, Direction direction )
828 {
829 childVect.clear();
830 ErrorCode rval = tool()->moab()->get_child_meshsets( mStack.back(), childVect );
831 if( MB_SUCCESS != rval ) return rval;
832 if( childVect.empty() ) return MB_ENTITY_NOT_FOUND;
833
834 BSPTree::Plane plane( plane_ref );
835 if( direction == RIGHT ) plane.flip();
836
837 Corners clipped_face;
838 rval = plane_cut_box( clipped_face.coords, leafCoords, plane );
839 if( MB_SUCCESS != rval ) return rval;
840
841 mStack.push_back( childVect[direction] );
842 stackData.push_back( clipped_face );
843 return MB_SUCCESS;
844 }
845
846 ErrorCode BSPTreeBoxIter::step( Direction direction )
847 {
848 EntityHandle node, parent;
849 Corners clipped_face;
850 ErrorCode rval;
851 BSPTree::Plane plane;
852 const Direction opposite = static_cast< Direction >( 1 - direction );
853
854
855
856
857 if( mStack.empty() ) return MB_FAILURE;
858
859
860
861
862 node = mStack.back();
863 mStack.pop_back();
864 clipped_face = stackData.back();
865 stackData.pop_back();
866
867 while( !mStack.empty() )
868 {
869
870 parent = mStack.back();
871 childVect.clear();
872 rval = tool()->moab()->get_child_meshsets( parent, childVect );
873 if( MB_SUCCESS != rval ) return rval;
874 rval = tool()->get_split_plane( parent, plane );
875 if( MB_SUCCESS != rval ) return rval;
876 if( direction == LEFT ) plane.flip();
877
878
879 if( childVect[opposite] == node )
880 {
881
882 plane_uncut_box( clipped_face.coords, leafCoords, plane );
883
884 plane.flip();
885 plane_cut_box( clipped_face.coords, leafCoords, plane );
886
887 mStack.push_back( childVect[direction] );
888 stackData.push_back( clipped_face );
889
890 return step_to_first_leaf( opposite );
891 }
892
893
894
895 assert( childVect[direction] == node );
896 plane.flip();
897 plane_uncut_box( clipped_face.coords, leafCoords, plane );
898 node = parent;
899 clipped_face = stackData.back();
900 mStack.pop_back();
901 stackData.pop_back();
902 }
903
904 return MB_ENTITY_NOT_FOUND;
905 }
906
907 ErrorCode BSPTreeBoxIter::get_box_corners( double coords[8][3] ) const
908 {
909 memcpy( coords, leafCoords, 24 * sizeof( double ) );
910 return MB_SUCCESS;
911 }
912
913
914 static void subtr( double result[3], const double a[3], const double b[3] )
915 {
916 result[0] = a[0] - b[0];
917 result[1] = a[1] - b[1];
918 result[2] = a[2] - b[2];
919 }
920
921
922 static void sum( double result[3], const double a[3], const double b[3], const double c[3], const double d[3] )
923 {
924 result[0] = a[0] + b[0] + c[0] + d[0];
925 result[1] = a[1] + b[1] + c[1] + d[1];
926 result[2] = a[2] + b[2] + c[2] + d[2];
927 }
928
929
930 static void cross( double result[3], const double a[3], const double b[3] )
931 {
932 result[0] = a[1] * b[2] - a[2] * b[1];
933 result[1] = a[2] * b[0] - a[0] * b[2];
934 result[2] = a[0] * b[1] - a[1] * b[0];
935 }
936
937 static double dot( const double a[3], const double b[3] )
938 {
939 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
940 }
941
942 double BSPTreeBoxIter::volume() const
943 {
944
945 double f1[3], f2[3], f3[3], f4[3], f5[3], f6[3];
946 sum( f1, leafCoords[0], leafCoords[1], leafCoords[4], leafCoords[5] );
947 sum( f2, leafCoords[1], leafCoords[2], leafCoords[5], leafCoords[6] );
948 sum( f3, leafCoords[2], leafCoords[3], leafCoords[6], leafCoords[7] );
949 sum( f4, leafCoords[0], leafCoords[3], leafCoords[4], leafCoords[7] );
950 sum( f5, leafCoords[0], leafCoords[1], leafCoords[2], leafCoords[3] );
951 sum( f6, leafCoords[4], leafCoords[5], leafCoords[6], leafCoords[7] );
952 double v13[3], v24[3], v65[3];
953 subtr( v13, f1, f3 );
954 subtr( v24, f2, f4 );
955 subtr( v65, f6, f5 );
956 double cr[3];
957 cross( cr, v13, v24 );
958 return ( 1. / 64 ) * dot( cr, v65 );
959 }
960
961 BSPTreeBoxIter::XSect BSPTreeBoxIter::splits( const BSPTree::Plane& plane ) const
962 {
963
964 unsigned result = 0;
965 for( unsigned i = 0; i < 8u; ++i )
966 {
967 double d = plane.signed_distance( leafCoords[i] );
968
969
970 if( fabs( d ) < BSPTree::epsilon() ) return NONHEX;
971
972 if( d > 0.0 ) result |= 1 << i;
973 }
974
975 switch( result )
976 {
977
978
979 case 0:
980 case 0xFF:
981 return MISS;
982
983
984
985
986 case B0154:
987 case B1265:
988 case B2376:
989 case B3047:
990 case B3210:
991 case B4567:
992 return SPLIT;
993
994
995
996 default:
997 return NONHEX;
998 }
999 }
1000
1001 bool BSPTreeBoxIter::intersects( const BSPTree::Plane& plane ) const
1002 {
1003
1004 unsigned count = 0;
1005 for( unsigned i = 0; i < 8u; ++i )
1006 count += plane.above( leafCoords[i] );
1007 return count > 0 && count < 8u;
1008 }
1009
1010 ErrorCode BSPTreeBoxIter::sibling_side( SideBits& side_out ) const
1011 {
1012 if( mStack.size() < 2 )
1013 return MB_ENTITY_NOT_FOUND;
1014
1015 EntityHandle parent = mStack[mStack.size() - 2];
1016 BSPTree::Plane plane;
1017 ErrorCode rval = tool()->get_split_plane( parent, plane );
1018 if( MB_SUCCESS != rval ) return MB_FAILURE;
1019
1020 side_out = side_on_plane( leafCoords, plane );
1021 return MB_SUCCESS;
1022 }
1023
1024 ErrorCode BSPTreeBoxIter::get_neighbors( SideBits side, std::vector< BSPTreeBoxIter >& results, double epsilon ) const
1025 {
1026 EntityHandle tmp_handle;
1027 BSPTree::Plane plane;
1028 ErrorCode rval;
1029 int n;
1030
1031 Corners face;
1032 rval = face_corners( side, leafCoords, face.coords );
1033 if( MB_SUCCESS != rval ) return rval;
1034
1035
1036
1037
1038 BSPTreeBoxIter iter( *this );
1039 for( ;; )
1040 {
1041 tmp_handle = iter.handle();
1042
1043 rval = iter.up();
1044 if( MB_SUCCESS != rval )
1045 return ( rval == MB_ENTITY_NOT_FOUND ) ? MB_SUCCESS : rval;
1046
1047 iter.childVect.clear();
1048 rval = tool()->moab()->get_child_meshsets( iter.handle(), iter.childVect );
1049 if( MB_SUCCESS != rval ) return rval;
1050
1051 rval = tool()->get_split_plane( iter.handle(), plane );
1052 if( MB_SUCCESS != rval ) return rval;
1053 SideBits s = side_above_plane( iter.leafCoords, plane );
1054
1055 if( tmp_handle == iter.childVect[0] && s == side )
1056 {
1057 rval = iter.down( plane, RIGHT );
1058 if( MB_SUCCESS != rval ) return rval;
1059 break;
1060 }
1061 else if( tmp_handle == iter.childVect[1] && opposite_face( s ) == side )
1062 {
1063 rval = iter.down( plane, LEFT );
1064 if( MB_SUCCESS != rval ) return rval;
1065 break;
1066 }
1067 }
1068
1069
1070 std::vector< BSPTreeBoxIter > list;
1071
1072 for( ;; )
1073 {
1074
1075
1076 for( ;; )
1077 {
1078 rval = tool()->moab()->num_child_meshsets( iter.handle(), &n );
1079 if( MB_SUCCESS != rval ) return rval;
1080
1081
1082 if( !n )
1083 {
1084 results.push_back( iter );
1085 break;
1086 }
1087
1088 rval = tool()->get_split_plane( iter.handle(), plane );
1089 if( MB_SUCCESS != rval ) return rval;
1090
1091 bool some_above = false, some_below = false;
1092 for( int i = 0; i < 4; ++i )
1093 {
1094 double signed_d = plane.signed_distance( face.coords[i] );
1095 if( signed_d > -epsilon ) some_above = true;
1096 if( signed_d < epsilon ) some_below = true;
1097 }
1098
1099 if( some_above && some_below )
1100 {
1101 list.push_back( iter );
1102 list.back().down( plane, RIGHT );
1103 iter.down( plane, LEFT );
1104 }
1105 else if( some_above )
1106 {
1107 iter.down( plane, RIGHT );
1108 }
1109 else if( some_below )
1110 {
1111 iter.down( plane, LEFT );
1112 }
1113 else
1114 {
1115
1116 return MB_FAILURE;
1117 }
1118 }
1119
1120 if( list.empty() ) break;
1121
1122 iter = list.back();
1123 list.pop_back();
1124 }
1125
1126 return MB_SUCCESS;
1127 }
1128
1129 ErrorCode BSPTreeBoxIter::calculate_polyhedron( BSPTreePoly& poly_out ) const
1130 {
1131 const CartVect* ptr = reinterpret_cast< const CartVect* >( leafCoords );
1132 return poly_out.set( ptr );
1133 }
1134
1135 ErrorCode BSPTree::leaf_containing_point( EntityHandle tree_root, const double point[3], EntityHandle& leaf_out )
1136 {
1137 std::vector< EntityHandle > children;
1138 Plane plane;
1139 EntityHandle node = tree_root;
1140 ErrorCode rval = moab()->get_child_meshsets( node, children );
1141 if( MB_SUCCESS != rval ) return rval;
1142 while( !children.empty() )
1143 {
1144 rval = get_split_plane( node, plane );
1145 if( MB_SUCCESS != rval ) return rval;
1146
1147 node = children[plane.above( point )];
1148 children.clear();
1149 rval = moab()->get_child_meshsets( node, children );
1150 if( MB_SUCCESS != rval ) return rval;
1151 }
1152 leaf_out = node;
1153 return MB_SUCCESS;
1154 }
1155
1156 ErrorCode BSPTree::leaf_containing_point( EntityHandle root, const double point[3], BSPTreeIter& iter )
1157 {
1158 ErrorCode rval;
1159
1160 rval = iter.initialize( this, root, point );
1161 if( MB_SUCCESS != rval ) return rval;
1162
1163 for( ;; )
1164 {
1165 iter.childVect.clear();
1166 rval = moab()->get_child_meshsets( iter.handle(), iter.childVect );
1167 if( MB_SUCCESS != rval || iter.childVect.empty() ) return rval;
1168
1169 Plane plane;
1170 rval = get_split_plane( iter.handle(), plane );
1171 if( MB_SUCCESS != rval ) return rval;
1172
1173 rval = iter.down( plane, ( BSPTreeIter::Direction )( plane.above( point ) ) );
1174 if( MB_SUCCESS != rval ) return rval;
1175 }
1176 }
1177
1178 template < typename PlaneIter >
1179 static inline bool ray_intersect_halfspaces( const CartVect& ray_pt,
1180 const CartVect& ray_dir,
1181 const PlaneIter& begin,
1182 const PlaneIter& end,
1183 double& t_enter,
1184 double& t_exit )
1185 {
1186 const double epsilon = 1e-12;
1187
1188
1189 t_enter = 0.0;
1190 t_exit = std::numeric_limits< double >::infinity();
1191
1192
1193
1194 for( PlaneIter i = begin; i != end; ++i )
1195 {
1196 CartVect norm( i->norm );
1197 double coeff = i->coeff;
1198 double den = norm % ray_dir;
1199 if( fabs( den ) < epsilon )
1200 {
1201 if( i->above( ray_pt.array() ) ) return false;
1202 }
1203 else
1204 {
1205 double t_xsect = ( -coeff - ( norm % ray_pt ) ) / den;
1206
1207 if( den > 0 )
1208 {
1209 if( t_xsect < t_exit ) t_exit = t_xsect;
1210 }
1211 else
1212 {
1213 if( t_xsect > t_enter ) t_enter = t_xsect;
1214 }
1215 }
1216 }
1217
1218 return t_exit >= t_enter;
1219 }
1220
1221 class BoxPlaneIter
1222 {
1223 int faceNum;
1224 BSPTree::Plane facePlanes[6];
1225
1226 public:
1227 BoxPlaneIter( const double coords[8][3] );
1228 BoxPlaneIter() : faceNum( 6 ) {}
1229 const BSPTree::Plane* operator->() const
1230 {
1231 return facePlanes + faceNum;
1232 }
1233 bool operator==( const BoxPlaneIter& other ) const
1234 {
1235 return faceNum == other.faceNum;
1236 }
1237 bool operator!=( const BoxPlaneIter& other ) const
1238 {
1239 return faceNum != other.faceNum;
1240 }
1241 BoxPlaneIter& operator++()
1242 {
1243 ++faceNum;
1244 return *this;
1245 }
1246 };
1247
1248 static const int box_face_corners[6][4] = { { 0, 1, 5, 4 }, { 1, 2, 6, 5 }, { 2, 3, 7, 6 },
1249 { 3, 0, 4, 7 }, { 3, 2, 1, 0 }, { 4, 5, 6, 7 } };
1250
1251 BoxPlaneIter::BoxPlaneIter( const double coords[8][3] ) : faceNum( 0 )
1252 {
1253
1254
1255 assert( sizeof( CartVect ) == sizeof( coords[0] ) );
1256 const CartVect* corners = reinterpret_cast< const CartVect* >( coords );
1257 for( int i = 0; i < 6; ++i )
1258 {
1259 const int* indices = box_face_corners[i];
1260 CartVect v1 = corners[indices[1]] - corners[indices[0]];
1261 CartVect v2 = corners[indices[3]] - corners[indices[0]];
1262 CartVect n = v1 * v2;
1263 facePlanes[i] = BSPTree::Plane( n.array(), -( n % corners[indices[2]] ) );
1264 }
1265 }
1266
1267 bool BSPTreeBoxIter::intersect_ray( const double ray_point[3],
1268 const double ray_vect[3],
1269 double& t_enter,
1270 double& t_exit ) const
1271 {
1272 BoxPlaneIter iter( this->leafCoords ), end;
1273 return ray_intersect_halfspaces( CartVect( ray_point ), CartVect( ray_vect ), iter, end, t_enter, t_exit );
1274 }
1275
1276 class BSPTreePlaneIter
1277 {
1278 BSPTree* toolPtr;
1279 const EntityHandle* const pathToRoot;
1280 int pathPos;
1281 BSPTree::Plane tmpPlane;
1282 std::vector< EntityHandle > tmpChildren;
1283
1284 public:
1285 BSPTreePlaneIter( BSPTree* tool, const EntityHandle* path, int path_len )
1286 : toolPtr( tool ), pathToRoot( path ), pathPos( path_len - 1 )
1287 {
1288 operator++();
1289 }
1290 BSPTreePlaneIter()
1291 : toolPtr( 0 ), pathToRoot( 0 ), pathPos( -1 )
1292 {
1293 }
1294
1295 const BSPTree::Plane* operator->() const
1296 {
1297 return &tmpPlane;
1298 }
1299 bool operator==( const BSPTreePlaneIter& other ) const
1300 {
1301 return pathPos == other.pathPos;
1302 }
1303 bool operator!=( const BSPTreePlaneIter& other ) const
1304 {
1305 return pathPos != other.pathPos;
1306 }
1307 BSPTreePlaneIter& operator++();
1308 };
1309
1310 BSPTreePlaneIter& BSPTreePlaneIter::operator++()
1311 {
1312 if( --pathPos < 0 ) return *this;
1313
1314 EntityHandle prev = pathToRoot[pathPos + 1];
1315 EntityHandle curr = pathToRoot[pathPos];
1316
1317 ErrorCode rval = toolPtr->get_split_plane( curr, tmpPlane );
1318 if( MB_SUCCESS != rval )
1319 {
1320 assert( false );
1321 pathPos = 0;
1322 return *this;
1323 }
1324
1325 tmpChildren.clear();
1326 rval = toolPtr->moab()->get_child_meshsets( curr, tmpChildren );
1327 if( MB_SUCCESS != rval || tmpChildren.size() != 2 )
1328 {
1329 assert( false );
1330 pathPos = 0;
1331 return *this;
1332 }
1333
1334 if( tmpChildren[1] == prev ) tmpPlane.flip();
1335 return *this;
1336 }
1337
1338 bool BSPTreeIter::intersect_ray( const double ray_point[3],
1339 const double ray_vect[3],
1340 double& t_enter,
1341 double& t_exit ) const
1342 {
1343
1344 BSPTreePlaneIter iter1( tool(), &mStack[0], mStack.size() ), end1;
1345 if( !ray_intersect_halfspaces( CartVect( ray_point ), CartVect( ray_vect ), iter1, end1, t_enter, t_exit ) )
1346 return false;
1347
1348
1349 double corners[8][3];
1350 ErrorCode rval = tool()->get_tree_box( mStack.front(), corners );
1351 if( MB_SUCCESS != rval )
1352 {
1353 assert( false );
1354 return false;
1355 }
1356
1357 BoxPlaneIter iter2( corners ), end2;
1358 double t2_enter, t2_exit;
1359 if( !ray_intersect_halfspaces( CartVect( ray_point ), CartVect( ray_vect ), iter2, end2, t2_enter, t2_exit ) )
1360 return false;
1361
1362
1363
1364 if( t_enter < t2_enter ) t_enter = t2_enter;
1365 if( t_exit > t2_exit ) t_exit = t2_exit;
1366 return t_enter <= t_exit;
1367 }
1368
1369 }