|
Intrepid
|
00001 // @HEADER 00002 // ************************************************************************ 00003 // 00004 // Intrepid Package 00005 // Copyright (2007) Sandia Corporation 00006 // 00007 // Under terms of Contract DE-AC04-94AL85000, there is a non-exclusive 00008 // license for use of this work by or on behalf of the U.S. Government. 00009 // 00010 // Redistribution and use in source and binary forms, with or without 00011 // modification, are permitted provided that the following conditions are 00012 // met: 00013 // 00014 // 1. Redistributions of source code must retain the above copyright 00015 // notice, this list of conditions and the following disclaimer. 00016 // 00017 // 2. Redistributions in binary form must reproduce the above copyright 00018 // notice, this list of conditions and the following disclaimer in the 00019 // documentation and/or other materials provided with the distribution. 00020 // 00021 // 3. Neither the name of the Corporation nor the names of the 00022 // contributors may be used to endorse or promote products derived from 00023 // this software without specific prior written permission. 00024 // 00025 // THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY 00026 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00027 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 00028 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE 00029 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 00030 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 00031 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00032 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00033 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00034 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00035 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00036 // 00037 // Questions: Alejandro Mota (amota@sandia.gov) 00038 // 00039 // ************************************************************************ 00040 // @HEADER 00041 00042 #if !defined(Intrepid_MiniTensor_Geometry_t_h) 00043 #define Intrepid_MiniTensor_Geometry_t_h 00044 00045 #include <iterator> 00046 00047 namespace Intrepid { 00048 00049 // 00050 // Length of a segment 00051 // 00052 template<typename T, Index N> 00053 T 00054 length(Vector<T, N> const & p0, Vector<T, N> const & p1) 00055 { 00056 Vector<T, N> const v = p1 - p0; 00057 return norm(v); 00058 } 00059 00060 // 00061 // Area of a triangle 00062 // 00063 template<typename T, Index N> 00064 T 00065 area(Vector<T, N> const & p0, Vector<T, N> const & p1, 00066 Vector<T, N> const & p2) 00067 { 00068 Vector<T, N> const u = p1 - p0; 00069 Vector<T, N> const v = p2 - p0; 00070 00071 T const base = norm(u); 00072 00073 Vector<T, N> const i = u / base; 00074 Vector<T, N> const n = v - dot(v, i) * i; 00075 00076 T const height = norm(n); 00077 T const area = 0.5 * base * height; 00078 00079 return area; 00080 } 00081 00082 // 00083 // Area of a quadrilateral, assumed planar. If not planar, returns 00084 // the sum of the areas of the two triangles p0,p1,p2 and p0,p2,p3 00085 // 00086 template<typename T, Index N> 00087 T 00088 area(Vector<T, N> const & p0, Vector<T, N> const & p1, 00089 Vector<T, N> const & p2, Vector<T, N> const & p3) 00090 { 00091 return area(p0, p1, p2) + area(p0, p2, p3); 00092 } 00093 00094 // 00095 // Volume of tetrahedron 00096 // 00097 template<typename T, Index N> 00098 T 00099 volume(Vector<T, N> const & p0, Vector<T, N> const & p1, 00100 Vector<T, N> const & p2, Vector<T, N> const & p3) 00101 { 00102 // Area of base triangle 00103 T const 00104 base = area(p0, p1, p2); 00105 00106 // Height 00107 Vector<T, N> const u = p1 - p0; 00108 Vector<T, N> const v = p2 - p0; 00109 Vector<T, N> const w = p3 - p0; 00110 00111 Vector<T, N> const i = u / norm(u); 00112 Vector<T, N> const j = v / norm(v); 00113 00114 Vector<T, N> const n = w - dot(w, i) * i - dot(w, j) * j; 00115 00116 T const height = norm(n); 00117 00118 // Volume 00119 T const volume = base * height / 3.0; 00120 00121 return volume; 00122 } 00123 00124 // 00125 // Volume of pyramid of quadrilateral base 00126 // Base is assumed planar 00127 // Base is p0,p1,p2,p3 00128 // Apex is p4 00129 // 00130 template<typename T, Index N> 00131 T 00132 volume(Vector<T, N> const & p0, Vector<T, N> const & p1, 00133 Vector<T, N> const & p2, Vector<T, N> const & p3, 00134 Vector<T, N> const & p4) 00135 { 00136 // Area of base quadrilateral 00137 T const 00138 base = area(p0, p1, p2, p3); 00139 00140 // Height 00141 Vector<T, N> const u = p1 - p0; 00142 Vector<T, N> const v = p2 - p0; 00143 Vector<T, N> const w = p4 - p0; 00144 00145 Vector<T, N> const i = u / norm(u); 00146 Vector<T, N> const j = v / norm(v); 00147 00148 Vector<T, N> const n = w - dot(w, i) * i - dot(w, j) * j; 00149 00150 T const height = norm(n); 00151 00152 // Volume 00153 T const volume = base * height / 3.0; 00154 00155 return volume; 00156 } 00157 00158 // 00159 // Volume of hexahedron 00160 // Assumption: all faces are planar 00161 // Decompose into 3 pyramids 00162 // 00163 template<typename T, Index N> 00164 T 00165 volume(Vector<T, N> const & p0, Vector<T, N> const & p1, 00166 Vector<T, N> const & p2, Vector<T, N> const & p3, 00167 Vector<T, N> const & p4, Vector<T, N> const & p5, 00168 Vector<T, N> const & p6, Vector<T, N> const & p7) 00169 { 00170 // 1st pyramid 00171 T const V1 = volume(p4, p7, p6, p5, p0); 00172 00173 // 2nd pyramid 00174 T const V2 = volume(p3, p2, p6, p7, p0); 00175 00176 // 3rd pyramid 00177 T const V3 = volume(p1, p5, p6, p2, p0); 00178 00179 return V1 + V2 + V3; 00180 } 00181 00182 // 00183 // Centroids of segment, triangle, tetrahedron, quadrilateral 00184 // and hexahedron 00185 // For these we can just take the average of the vertices. 00186 // WARNING: This is not the center of mass. 00187 // 00188 template<typename T, Index N> 00189 Vector<T, N> 00190 centroid(std::vector<Vector<T, N> > const & points) 00191 { 00192 Vector<T, N> C(points[0].get_dimension()); 00193 C.clear(); 00194 typedef typename std::vector<Vector<T, N> >::size_type sizeT; 00195 sizeT const n = points.size(); 00196 00197 for (sizeT i = 0; i < n; ++i) { 00198 C += points[i]; 00199 } 00200 return C / static_cast<T>(n); 00201 } 00202 00203 // 00204 // The surface normal of a face 00205 // Input: 3 independent nodes on the face 00206 // Output: unit normal vector 00207 // 00208 template<typename T, Index N> 00209 Vector<T, N> 00210 normal(Vector<T, N> const & p0, 00211 Vector<T, N> const & p1, 00212 Vector<T, N> const & p2) 00213 { 00214 // Construct 2 independent vectors 00215 Vector<T, N> const v0 = p1 - p0; 00216 Vector<T, N> const v1 = p2 - p0; 00217 00218 Vector<T, N> const n = unit(cross(v0, v1)); 00219 00220 return n; 00221 } 00222 00223 // 00224 // Given 3 points p0, p1, p2 that define a plane 00225 // determine if point p is in the same side of the normal 00226 // to the plane as defined by the right hand rule. 00227 // 00228 template<typename T, Index N> 00229 bool 00230 in_normal_side( 00231 Vector<T, N> const & p, 00232 Vector<T, N> const & p0, 00233 Vector<T, N> const & p1, 00234 Vector<T, N> const & p2, 00235 T const tolerance) 00236 { 00237 Vector<T, N> const v0 = p1 - p0; 00238 Vector<T, N> const v1 = p2 - p0; 00239 00240 T const h = std::min(norm(v0), norm(v1)); 00241 00242 Vector<T, N> const n = unit(cross(v0, v1)); 00243 Vector<T, N> const v = p - p0; 00244 00245 T const s = dot(v, n); 00246 00247 if (s < -tolerance * h) return false; 00248 00249 return true; 00250 } 00251 00252 // 00253 // Given two iterators to a container of points, 00254 // find the associated bounding box. 00255 // \param start, end: define sequence of points 00256 // \return vectors that define the bounding box 00257 // 00258 template<typename T, typename I, Index N> 00259 std::pair< Vector<T, N>, Vector<T, N> > 00260 bounding_box(I start, I end) 00261 { 00262 I 00263 it = start; 00264 00265 Vector<T, N> 00266 min = (*it); 00267 00268 Vector<T, N> 00269 max = min; 00270 00271 Index const 00272 dimension = min.get_dimension(); 00273 00274 ++it; 00275 00276 for (; it != end; ++it) { 00277 00278 Vector<T, N> const & 00279 point = (*it); 00280 00281 for (Index i = 0; i < dimension; ++i) { 00282 T const s = point(i); 00283 if (s < min(i)) min(i) = s; 00284 if (s > max(i)) max(i) = s; 00285 } 00286 00287 } 00288 00289 return std::make_pair(min, max); 00290 } 00291 00292 template<typename T, typename I> 00293 std::pair< Vector<T, DYNAMIC>, Vector<T, DYNAMIC> > 00294 bounding_box(I start, I end) 00295 { 00296 return bounding_box<T, I, DYNAMIC>(start, end); 00297 } 00298 00299 // 00300 // Determine if a given point is inside a bounding box. 00301 // \param p the point 00302 // \param min, max points defining the box 00303 // \return whether the point is inside 00304 // 00305 template<typename T, Index N> 00306 bool 00307 in_box( 00308 Vector<T, N> const & p, 00309 Vector<T, N> const & min, 00310 Vector<T, N> const & max) 00311 { 00312 Index const 00313 dimension = p.get_dimension(); 00314 00315 assert(min.get_dimension() == dimension); 00316 assert(max.get_dimension() == dimension); 00317 00318 for (Index i = 0; i < dimension; ++i) { 00319 T const & s = p(i); 00320 if (s < min(i)) return false; 00321 if (s > max(i)) return false; 00322 } 00323 00324 return true; 00325 } 00326 00327 // 00328 // Generate random point inside bounding box 00329 // \param min, max the bounding box 00330 // \return p point inside box 00331 // 00332 template<typename T, Index N> 00333 Vector<T, N> 00334 random_in_box(Vector<T, N> const & min, Vector<T, N> const & max) 00335 { 00336 Index const 00337 dimension = min.get_dimension(); 00338 00339 assert(max.get_dimension() == dimension); 00340 00341 Vector<T, N> p(dimension); 00342 00343 for (Index i = 0; i < dimension; ++i) { 00344 p(i) = (max(i) - min(i)) * T(std::rand())/T(RAND_MAX) + min(i); 00345 } 00346 00347 return p; 00348 } 00349 00350 // 00351 // Given 4 points p0, p1, p2, p3 that define a tetrahedron 00352 // determine if point p is inside it. 00353 // 00354 template<typename T, Index N> 00355 bool 00356 in_tetrahedron( 00357 Vector<T, N> const & p, 00358 Vector<T, N> const & p0, 00359 Vector<T, N> const & p1, 00360 Vector<T, N> const & p2, 00361 Vector<T, N> const & p3, 00362 T const tolerance) 00363 { 00364 if (in_normal_side(p, p0, p1, p2, tolerance) == false) { 00365 00366 return false; 00367 00368 } else if (in_normal_side(p, p0, p3, p1, tolerance) == false) { 00369 00370 return false; 00371 00372 } else if (in_normal_side(p, p1, p3, p2, tolerance) == false) { 00373 00374 return false; 00375 00376 } else if (in_normal_side(p, p2, p3, p0, tolerance) == false) { 00377 00378 return false; 00379 00380 } 00381 00382 return true; 00383 } 00384 00385 // 00386 // Given 8 points that define a hexahedron 00387 // determine if point p is inside it. 00388 // Assumption: faces are planar 00389 // 00390 template<typename T, Index N> 00391 bool 00392 in_hexahedron( 00393 Vector<T, N> const & p, 00394 Vector<T, N> const & p0, 00395 Vector<T, N> const & p1, 00396 Vector<T, N> const & p2, 00397 Vector<T, N> const & p3, 00398 Vector<T, N> const & p4, 00399 Vector<T, N> const & p5, 00400 Vector<T, N> const & p6, 00401 Vector<T, N> const & p7, 00402 T const tolerance) 00403 { 00404 if (in_normal_side(p, p0, p1, p2, tolerance) == false) { 00405 00406 return false; 00407 00408 } else if (in_normal_side(p, p0, p4, p5, tolerance) == false) { 00409 00410 return false; 00411 00412 } else if (in_normal_side(p, p1, p5, p6, tolerance) == false) { 00413 00414 return false; 00415 00416 } else if (in_normal_side(p, p2, p6, p7, tolerance) == false) { 00417 00418 return false; 00419 00420 } else if (in_normal_side(p, p3, p7, p4, tolerance) == false) { 00421 00422 return false; 00423 00424 } else if (in_normal_side(p, p4, p7, p6, tolerance) == false) { 00425 00426 return false; 00427 00428 } 00429 00430 return true; 00431 } 00432 00433 // 00434 // Closest point 00435 // \param p the point 00436 // \param n vector of points to test 00437 // \return index to closest point 00438 // 00439 template<typename T, Index N> 00440 typename std::vector< Vector<T, N> >::size_type 00441 closest_point(Vector<T, N> const & p, std::vector< Vector<T, N> > const & n) 00442 { 00443 assert(n.size() > 0); 00444 00445 typename std::vector< Vector<T, N> >::size_type 00446 index = 0; 00447 00448 Vector<T, N> const 00449 v0 = p - n[0]; 00450 00451 T 00452 min = norm_square(v0); 00453 00454 for (typename std::vector< Vector<T, N> >::size_type i = 1; 00455 i < n.size(); 00456 ++i) { 00457 00458 Vector<T, N> const 00459 vi = p - n[i]; 00460 00461 T const 00462 s = norm_square(vi); 00463 00464 if (s < min) { 00465 min = s; 00466 index = i; 00467 } 00468 00469 } 00470 00471 return index; 00472 } 00473 00474 // Median of a sequence defined by random 00475 // access iterators. Undefined for empty set. 00476 // \param begin, end Iterators that define the sequence 00477 // \return median of sequence 00478 // 00479 template<typename T, typename Iterator> 00480 T 00481 median(Iterator begin, Iterator end) 00482 { 00483 // Firewall 00484 if (begin == end) { 00485 std::cerr << "ERROR: " << __PRETTY_FUNCTION__; 00486 std::cerr << std::endl; 00487 std::cerr << "Median undefined for empty set." << std::endl; 00488 exit(1); 00489 } 00490 00491 Index const 00492 size = static_cast<Index>(std::distance(begin, end)); 00493 00494 T 00495 median; 00496 00497 Index const 00498 mid_index = size / 2; 00499 00500 Iterator 00501 mid_iterator = begin + mid_index; 00502 00503 std::partial_sort(begin, mid_iterator, end); 00504 00505 if (size % 2 == 0) { 00506 00507 // Even number of elements 00508 T const 00509 b = *mid_iterator; 00510 00511 Iterator 00512 previous = mid_iterator - 1; 00513 00514 T const 00515 a = *previous; 00516 00517 median = (a + b) / 2.0; 00518 00519 } else { 00520 00521 // Odd number of elements 00522 median = *mid_iterator; 00523 00524 } 00525 00526 return median; 00527 } 00528 00529 // 00530 // Given quadrilateral nodes and a position 00531 // in parametric coordinates, interpolate. 00532 // \param xi position in parametric coordinates 00533 // \param p0 ... corner nodes 00534 // \return interpolated position 00535 // 00536 template<typename T, Index N> 00537 Vector<T, N> 00538 interpolate_quadrilateral( 00539 Vector<T, dimension_const<N, 2>::value> & xi, 00540 Vector<T, N> const & p0, 00541 Vector<T, N> const & p1, 00542 Vector<T, N> const & p2, 00543 Vector<T, N> const & p3) 00544 { 00545 00546 T const 00547 N0 = 0.25 * (1 - xi(0)) * (1 - xi(1)); 00548 00549 T const 00550 N1 = 0.25 * (1 + xi(0)) * (1 - xi(1)); 00551 00552 T const 00553 N2 = 0.25 * (1 + xi(0)) * (1 + xi(1)); 00554 00555 T const 00556 N3 = 0.25 * (1 - xi(0)) * (1 + xi(1)); 00557 00558 Vector<T, N> const 00559 p = N0 * p0 + N1 * p1 + N2 * p2 + N3 * p3; 00560 00561 return p; 00562 } 00563 00564 // 00565 // Given triangle nodes and a position 00566 // in parametric coordinates, interpolate. 00567 // \param xi position in parametric coordinates 00568 // \param p0 ... corner nodes 00569 // \return interpolated position 00570 // 00571 template<typename T, Index N> 00572 Vector<T, N> 00573 interpolate_triangle( 00574 Vector<T, dimension_const<N, 3>::value> & xi, 00575 Vector<T, N> const & p0, 00576 Vector<T, N> const & p1, 00577 Vector<T, N> const & p2) 00578 { 00579 xi(2) = 1.0 - xi(0) - xi(1); 00580 00581 Vector<T, N> const 00582 p = xi(0) * p0 + xi(1) * p1 + xi(2) * p2; 00583 00584 return p; 00585 } 00586 00587 // 00588 // Given hexahedron nodes and a position 00589 // in parametric coordinates, interpolate. 00590 // \param xi position in parametric coordinates 00591 // \param p0 ... corner nodes 00592 // \return interpolated position 00593 // 00594 template<typename T, Index N> 00595 Vector<T, N> 00596 interpolate_hexahedron( 00597 Vector<T, dimension_const<N, 3>::value> & xi, 00598 Vector<T, N> const & p0, 00599 Vector<T, N> const & p1, 00600 Vector<T, N> const & p2, 00601 Vector<T, N> const & p3, 00602 Vector<T, N> const & p4, 00603 Vector<T, N> const & p5, 00604 Vector<T, N> const & p6, 00605 Vector<T, N> const & p7) 00606 { 00607 00608 T const 00609 N0 = 0.125 * (1 - xi(0)) * (1 - xi(1)) * (1 - xi(2)); 00610 00611 T const 00612 N1 = 0.125 * (1 + xi(0)) * (1 - xi(1)) * (1 - xi(2)); 00613 00614 T const 00615 N2 = 0.125 * (1 + xi(0)) * (1 + xi(1)) * (1 - xi(2)); 00616 00617 T const 00618 N3 = 0.125 * (1 - xi(0)) * (1 + xi(1)) * (1 - xi(2)); 00619 00620 T const 00621 N4 = 0.125 * (1 - xi(0)) * (1 - xi(1)) * (1 + xi(2)); 00622 00623 T const 00624 N5 = 0.125 * (1 + xi(0)) * (1 - xi(1)) * (1 + xi(2)); 00625 00626 T const 00627 N6 = 0.125 * (1 + xi(0)) * (1 + xi(1)) * (1 + xi(2)); 00628 00629 T const 00630 N7 = 0.125 * (1 - xi(0)) * (1 + xi(1)) * (1 + xi(2)); 00631 00632 Vector<T, N> const 00633 p = 00634 N0 * p0 + N1 * p1 + N2 * p2 + N3 * p3 + 00635 N4 * p4 + N5 * p5 + N6 * p6 + N7 * p7; 00636 00637 return p; 00638 } 00639 00640 // 00641 // Given tetrahedron nodes and a position 00642 // in parametric coordinates, interpolate. 00643 // \param xi position in parametric coordinates 00644 // \param p0 ... corner nodes 00645 // \return interpolated position 00646 // 00647 template<typename T, Index N> 00648 Vector<T, N> 00649 interpolate_tetrahedron( 00650 Vector<T, dimension_const<N, 4>::value> & xi, 00651 Vector<T, N> const & p0, 00652 Vector<T, N> const & p1, 00653 Vector<T, N> const & p2, 00654 Vector<T, N> const & p3) 00655 { 00656 xi(3) = 1.0 - xi(0) - xi(1) - xi(2); 00657 00658 Vector<T, N> const 00659 p = xi(0) * p0 + xi(1) * p1 + xi(2) * p2 + xi(3) * p3; 00660 00661 return p; 00662 } 00663 00672 template<typename T, Index M, Index N> 00673 Vector<T, N> 00674 interpolate_element( 00675 ELEMENT::Type element_type, 00676 Vector<T, M> & xi, 00677 std::vector< Vector<T, N> > const & v) 00678 { 00679 Vector<T, N> p; 00680 00681 switch (element_type) { 00682 00683 case ELEMENT::TRIANGULAR: 00684 p = interpolate_triangle(xi, v[0], v[1], v[2]); 00685 break; 00686 00687 case ELEMENT::QUADRILATERAL: 00688 p = interpolate_quadrilateral(xi, v[0], v[1], v[2], v[3]); 00689 break; 00690 00691 case ELEMENT::TETRAHEDRAL: 00692 p = interpolate_tetrahedron(xi, v[0], v[1], v[2], v[3]); 00693 break; 00694 00695 case ELEMENT::HEXAHEDRAL: 00696 p = interpolate_hexahedron( 00697 xi, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); 00698 break; 00699 00700 default: 00701 std::cerr << "ERROR: " << __PRETTY_FUNCTION__; 00702 std::cerr << std::endl; 00703 std::cerr << "Unknown element type in interpolation."; 00704 std::cerr << std::endl; 00705 exit(1); 00706 break; 00707 00708 } 00709 00710 return p; 00711 } 00712 00713 // 00714 // Given a vector of points, determine 00715 // distances between all of them. 00716 // \param vector of points 00717 // \return distance matrix 00718 // 00719 template<typename T, Index N> 00720 std::vector< std::vector<T> > 00721 distance_matrix(std::vector< Vector<T, N> > const & points) 00722 { 00723 Index const 00724 number_points = points.size(); 00725 00726 std::vector< std::vector<T> > 00727 distances(number_points); 00728 00729 for (Index i = 0; i < number_points; ++i) { 00730 00731 distances[i].resize(number_points); 00732 00733 distances[i][i] = 0.0; 00734 00735 for (Index j = i + 1; j < number_points; ++j) { 00736 00737 T const 00738 distance = norm(points[i] - points[j]); 00739 00740 distances[i][j] = distance; 00741 distances[j][i] = distance; 00742 00743 } 00744 00745 } 00746 00747 return distances; 00748 } 00749 00750 // 00751 // Given a distance matrix, determine the minimum 00752 // distance between two distinct points. 00753 // \param distance matrix 00754 // \return minimum distance 00755 // 00756 template<typename T> 00757 std::vector<T> 00758 minimum_distances(std::vector< std::vector<T> > const & distances) 00759 { 00760 Index const 00761 number_points = distances.size(); 00762 00763 std::vector<T> 00764 minima(number_points); 00765 00766 // First row 00767 T 00768 minimum = distances[0][1]; 00769 00770 for (Index j = 2; j < number_points; ++j) { 00771 minimum = std::min(minimum, distances[0][j]); 00772 } 00773 00774 minima[0] = minimum; 00775 00776 // Remaining rows 00777 for (Index i = 1; i < number_points; ++i) { 00778 00779 minimum = distances[i][0]; 00780 00781 for (Index j = 1; j < number_points; ++j) { 00782 00783 if (i == j) continue; 00784 00785 minimum = std::min(minimum, distances[i][j]); 00786 00787 } 00788 00789 minima[i] = minimum; 00790 00791 } 00792 00793 return minima; 00794 } 00795 00796 } // namespace Intrepid 00797 00798 #endif // Intrepid_MiniTensor_Geometry_t_h
1.7.6.1