source: src/Tesselation/BoundaryTriangleSet.cpp

Candidate_v1.6.1
Last change on this file was 9eb71b3, checked in by Frederik Heber <frederik.heber@…>, 8 years ago

Commented out MemDebug include and Memory::ignore.

  • MemDebug clashes with various allocation operators that use a specific placement in memory. It is so far not possible to wrap new/delete fully. Hence, we stop this effort which so far has forced us to put ever more includes (with clashes) into MemDebug and thereby bloat compilation time.
  • MemDebug does not add that much usefulness which is not also provided by valgrind.
  • Property mode set to 100644
File size: 21.2 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2010-2012 University of Bonn. All rights reserved.
5 *
6 *
7 * This file is part of MoleCuilder.
8 *
9 * MoleCuilder is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * MoleCuilder is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * BoundaryTriangleSet.cpp
25 *
26 * Created on: Jul 29, 2010
27 * Author: heber
28 */
29
30// include config.h
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34
35//#include "CodePatterns/MemDebug.hpp"
36
37#include "BoundaryTriangleSet.hpp"
38
39#include <iostream>
40
41#include "BoundaryLineSet.hpp"
42#include "BoundaryPointSet.hpp"
43#include "Atom/TesselPoint.hpp"
44
45#include "Helpers/defs.hpp"
46
47#include "CodePatterns/Assert.hpp"
48#include "CodePatterns/Info.hpp"
49#include "CodePatterns/Log.hpp"
50#include "CodePatterns/Verbose.hpp"
51#include "LinearAlgebra/Exceptions.hpp"
52#include "LinearAlgebra/Line.hpp"
53#include "LinearAlgebra/Plane.hpp"
54#include "LinearAlgebra/Vector.hpp"
55
56using namespace std;
57
58/** Constructor for BoundaryTriangleSet.
59 */
60BoundaryTriangleSet::BoundaryTriangleSet() :
61 Nr(-1)
62{
63 //Info FunctionInfo(__func__);
64 for (int i = 0; i < 3; i++) {
65 endpoints[i] = NULL;
66 lines[i] = NULL;
67 }
68}
69;
70
71/** Constructor for BoundaryTriangleSet with three lines.
72 * \param *line[3] lines that make up the triangle
73 * \param number number of triangle
74 */
75BoundaryTriangleSet::BoundaryTriangleSet(class BoundaryLineSet * const line[3], const int number) :
76 Nr(number)
77{
78 //Info FunctionInfo(__func__);
79 // set number
80 // set lines
81 for (int i = 0; i < 3; i++) {
82 lines[i] = line[i];
83 lines[i]->AddTriangle(this);
84 }
85 // get ascending order of endpoints
86 PointMap OrderMap;
87 for (int i = 0; i < 3; i++) {
88 // for all three lines
89 for (int j = 0; j < 2; j++) { // for both endpoints
90 OrderMap.insert(pair<int, class BoundaryPointSet *> (line[i]->endpoints[j]->Nr, line[i]->endpoints[j]));
91 // and we don't care whether insertion fails
92 }
93 }
94 // set endpoints
95 int Counter = 0;
96 LOG(4, "DEBUG: New triangle " << Nr << " with end points ... and lines:");
97 for (PointMap::iterator runner = OrderMap.begin(); runner != OrderMap.end(); runner++) {
98 endpoints[Counter] = runner->second;
99 LOG(4, "DEBUG: " << *endpoints[Counter] << "\t\t" << *lines[Counter]);
100 Counter++;
101 }
102 ASSERT(Counter >= 3,"We have a triangle with only two distinct endpoints!");
103};
104
105
106/** Destructor of BoundaryTriangleSet.
107 * Removes itself from each of its lines' LineMap and removes them if necessary.
108 * \note When removing triangles from a class Tesselation, use RemoveTesselationTriangle()
109 */
110BoundaryTriangleSet::~BoundaryTriangleSet()
111{
112 //Info FunctionInfo(__func__);
113 for (int i = 0; i < 3; i++) {
114 if (lines[i] != NULL) {
115 if (lines[i]->triangles.erase(Nr)) {
116 //LOG(5, "DEBUG: Triangle Nr." << Nr << " erased in line " << *lines[i] << ".");
117 }
118 if (lines[i]->triangles.empty()) {
119 //LOG(5, "DEBUG: " << *lines[i] << " is no more attached to any triangle, erasing.");
120 delete (lines[i]);
121 lines[i] = NULL;
122 }
123 }
124 }
125 //LOG(5, "DEBUG: Erasing triangle Nr." << Nr << " itself.");
126}
127;
128
129/** Calculates the area of this triangle.
130 *
131 * @return surface area in between the tree points of this triangle
132 */
133double BoundaryTriangleSet::getArea() const
134{
135 Vector x;
136 Vector y;
137 x = getEndpoint(0) - getEndpoint(1);
138 y = getEndpoint(0) - getEndpoint(2);
139 const double a = x.Norm();
140 const double b = y.Norm();
141 const double c = getEndpoint(2).distance(getEndpoint(1));
142 const double area = sqrt(((a + b + c) * (a + b + c) - 2 * (a * a + b * b + c * c)) / 16.); // area of tesselated triangle
143 return area;
144}
145
146/** Calculates the normal vector for this triangle.
147 * Is made unique by comparison with \a OtherVector to point in the other direction.
148 * \param &OtherVector direction vector to make normal vector unique.
149 */
150void BoundaryTriangleSet::GetNormalVector(const Vector &OtherVector)
151{
152 //Info FunctionInfo(__func__);
153 // get normal vector
154 NormalVector = Plane((endpoints[0]->node->getPosition()),
155 (endpoints[1]->node->getPosition()),
156 (endpoints[2]->node->getPosition())).getNormal();
157
158 // make it always point inward (any offset vector onto plane projected onto normal vector suffices)
159 if (NormalVector.ScalarProduct(OtherVector) > 0.)
160 NormalVector.Scale(-1.);
161 LOG(4, "DEBUG: Normal Vector of " << *this << " is " << NormalVector << ".");
162}
163;
164
165/** Finds the point on the triangle \a *BTS through which the line defined by \a *MolCenter and \a *x crosses.
166 * We call Vector::GetIntersectionWithPlane() to receive the intersection point with the plane
167 * Thus we test if it's really on the plane and whether it's inside the triangle on the plane or not.
168 * The latter is done as follows: We calculate the cross point of one of the triangle's baseline with the line
169 * given by the intersection and the third basepoint. Then, we check whether it's on the baseline (i.e. between
170 * the first two basepoints) or not.
171 * \param *out output stream for debugging
172 * \param &MolCenter offset vector of line
173 * \param &x second endpoint of line, minus \a *MolCenter is directional vector of line
174 * \param &Intersection intersection on plane on return
175 * \return true - \a *Intersection contains intersection on plane defined by triangle, false - zero vector if outside of triangle.
176 */
177
178bool BoundaryTriangleSet::GetIntersectionInsideTriangle(const Vector & MolCenter, const Vector & x, Vector &Intersection) const
179{
180 //Info FunctionInfo(__func__);
181 Vector CrossPoint;
182 Vector helper;
183
184 try {
185 Line centerLine = makeLineThrough(MolCenter, x);
186 Intersection = Plane(NormalVector, (endpoints[0]->node->getPosition())).GetIntersection(centerLine);
187
188 LOG(4, "DEBUG: Triangle is " << *this << ".");
189 LOG(4, "DEBUG: Line is from " << MolCenter << " to " << x << ".");
190 LOG(4, "DEBUG: Intersection is " << Intersection << ".");
191
192 if (Intersection.DistanceSquared(endpoints[0]->node->getPosition()) < MYEPSILON) {
193 LOG(4, "DEBUG: Intersection coindices with first endpoint.");
194 return true;
195 } else if (Intersection.DistanceSquared(endpoints[1]->node->getPosition()) < MYEPSILON) {
196 LOG(4, "DEBUG: Intersection coindices with second endpoint.");
197 return true;
198 } else if (Intersection.DistanceSquared(endpoints[2]->node->getPosition()) < MYEPSILON) {
199 LOG(4, "DEBUG: Intersection coindices with third endpoint.");
200 return true;
201 }
202 // Calculate cross point between one baseline and the line from the third endpoint to intersection
203 int i = 0;
204 do {
205 Line line1 = makeLineThrough((endpoints[i%3]->node->getPosition()),(endpoints[(i+1)%3]->node->getPosition()));
206 Line line2 = makeLineThrough((endpoints[(i+2)%3]->node->getPosition()),Intersection);
207 CrossPoint = line1.getIntersection(line2);
208 helper = (endpoints[(i+1)%3]->node->getPosition()) - (endpoints[i%3]->node->getPosition());
209 CrossPoint -= (endpoints[i%3]->node->getPosition()); // cross point was returned as absolute vector
210 const double s = CrossPoint.ScalarProduct(helper)/helper.NormSquared();
211 LOG(4, "DEBUG: Factor s is " << s << ".");
212 if ((s < -MYEPSILON) || ((s-1.) > MYEPSILON)) {
213 LOG(4, "DEBUG: Crosspoint " << CrossPoint << "outside of triangle.");
214 return false;
215 }
216 i++;
217 } while (i < 3);
218 LOG(4, "DEBUG: Crosspoint " << CrossPoint << " inside of triangle.");
219 return true;
220 }
221 catch (LinearAlgebraException &excp) {
222 LOG(1, boost::diagnostic_information(excp));
223 ELOG(1, "Alas! Intersection with plane failed - at least numerically - the intersection is not on the plane!");
224 return false;
225 }
226 return true;
227}
228
229
230/** Finds the point on the triangle to the point \a *x.
231 * We call Vector::GetIntersectionWithPlane() with \a * and the center of the triangle to receive an intersection point.
232 * Then we check the in-plane part (the part projected down onto plane). We check whether it crosses one of the
233 * boundary lines. If it does, we return this intersection as closest point, otherwise the projected point down.
234 * Thus we test if it's really on the plane and whether it's inside the triangle on the plane or not.
235 * The latter is done as follows: We calculate the cross point of one of the triangle's baseline with the line
236 * given by the intersection and the third basepoint. Then, we check whether it's on the baseline (i.e. between
237 * the first two basepoints) or not.
238 * \param *x point
239 * \param *ClosestPoint desired closest point inside triangle to \a *x, is absolute vector
240 * \return Distance squared between \a *x and closest point inside triangle
241 */
242double BoundaryTriangleSet::GetClosestPointInsideTriangle(const Vector &x, Vector &ClosestPoint) const
243{
244 //Info FunctionInfo(__func__);
245 Vector Direction;
246
247 // 1. get intersection with plane
248 LOG(4, "DEBUG: Looking for closest point of triangle " << *this << " to " << x << ".");
249 LOG(5, "DEBUG: endpoints are " << endpoints[0]->node->getPosition() << ","
250 << endpoints[1]->node->getPosition() << ", and " << endpoints[2]->node->getPosition() << ".");
251 try {
252 ClosestPoint = Plane(NormalVector, (endpoints[0]->node->getPosition())).getClosestPoint(x);
253 }
254 catch (LinearAlgebraException &excp) {
255 (ClosestPoint) = (x);
256 }
257 Vector InPlane(ClosestPoint); // points from plane intersection to straight-down point
258 LOG(5, "DEBUG: Closest point on triangle plane is " << ClosestPoint << ".");
259
260 // 2. Calculate in plane part of line (x, intersection)
261
262 // Calculate cross point between one baseline and the desired point such that distance is shortest
263 Vector CrossDirection[3];
264 Vector CrossPoint[3];
265 for (int i = 0; i < 3; i++) {
266 const Vector Direction = (endpoints[i%3]->node->getPosition()) - (endpoints[(i+1)%3]->node->getPosition());
267 // calculate intersection, line can never be parallel to Direction (is the same vector as PlaneNormal);
268 Line l = makeLineThrough((endpoints[i%3]->node->getPosition()), (endpoints[(i+1)%3]->node->getPosition()));
269 CrossPoint[i] = l.getClosestPoint(InPlane);
270 // NOTE: direction of line is normalized, hence s must not necessarily be in [0,1] for the baseline
271 LOG(5, "DEBUG: Closest point on line from " << (endpoints[(i+1)%3]->node->getPosition())
272 << " to " << (endpoints[i%3]->node->getPosition()) << " is " << CrossPoint[i] << ".");
273 CrossPoint[i] -= (endpoints[(i+1)%3]->node->getPosition()); // cross point was returned as absolute vector
274 const double s = CrossPoint[i].ScalarProduct(Direction)/Direction.NormSquared();
275 LOG(6, "DEBUG: Factor s is " << s << ".");
276 if ((s >= -MYEPSILON) && ((s-1.) <= MYEPSILON)) {
277 CrossPoint[i] += (endpoints[(i+1)%3]->node->getPosition()); // make cross point absolute again
278 LOG(6, "DEBUG: Crosspoint is " << CrossPoint[i] << ", intersecting BoundaryLine between "
279 << endpoints[i % 3]->node->getPosition() << " and "
280 << endpoints[(i + 1) % 3]->node->getPosition() << ".");
281 } else {
282 // set to either endpoint of BoundaryLine
283 if (s < -MYEPSILON)
284 CrossPoint[i] = (endpoints[(i+1)%3]->node->getPosition());
285 else
286 CrossPoint[i] = (endpoints[i%3]->node->getPosition());
287 LOG(6, "DEBUG: Crosspoint is " << CrossPoint[i] << ", intersecting outside of BoundaryLine between "
288 << endpoints[i % 3]->node->getPosition() << " and "
289 << endpoints[(i + 1) % 3]->node->getPosition() << ".");
290 }
291 CrossDirection[i] = CrossPoint[i] - InPlane;
292 }
293
294 bool InsideFlag = true;
295 double ShortestDistance = -1.;
296 for (int i = 0; i < 3; i++) {
297 const double sign = CrossDirection[i].ScalarProduct(CrossDirection[(i + 1) % 3]);
298 const double othersign = CrossDirection[i].ScalarProduct(CrossDirection[(i + 2) % 3]);
299
300 if ((sign > -MYEPSILON) && (othersign > -MYEPSILON)) // have different sign
301 InsideFlag = false;
302 // update current best candidate
303 const double distance = CrossPoint[i].DistanceSquared(x);
304 if ((ShortestDistance < 0.) || (ShortestDistance > distance)) {
305 ShortestDistance = distance;
306 (ClosestPoint) = CrossPoint[i];
307 }
308 }
309
310 if (InsideFlag) {
311 (ClosestPoint) = InPlane;
312 ShortestDistance = InPlane.DistanceSquared(x);
313 }
314 LOG(4, "DEBUG: Closest Point is " << ClosestPoint << " with shortest squared distance is " << ShortestDistance << ".");
315
316 return ShortestDistance;
317}
318
319
320/** Checks whether lines is any of the three boundary lines this triangle contains.
321 * \param *line line to test
322 * \return true - line is of the triangle, false - is not
323 */
324bool BoundaryTriangleSet::ContainsBoundaryLine(const BoundaryLineSet * const line) const
325{
326 //Info FunctionInfo(__func__);
327 for (int i = 0; i < 3; i++)
328 if (line == lines[i])
329 return true;
330 return false;
331}
332;
333
334/** Checks whether point is any of the three endpoints this triangle contains.
335 * \param *point point to test
336 * \return true - point is of the triangle, false - is not
337 */
338bool BoundaryTriangleSet::ContainsBoundaryPoint(const BoundaryPointSet * const point) const
339{
340 //Info FunctionInfo(__func__);
341 for (int i = 0; i < 3; i++)
342 if (point == endpoints[i])
343 return true;
344 return false;
345}
346;
347
348/** Checks whether point is any of the three endpoints this triangle contains.
349 * \param *point TesselPoint to test
350 * \return true - point is of the triangle, false - is not
351 */
352bool BoundaryTriangleSet::ContainsBoundaryPoint(const TesselPoint * const point) const
353{
354 //Info FunctionInfo(__func__);
355 for (int i = 0; i < 3; i++)
356 if (point == endpoints[i]->node)
357 return true;
358 return false;
359}
360;
361
362/** Checks whether three given \a *Points coincide with triangle's endpoints.
363 * \param *Points[3] pointer to BoundaryPointSet
364 * \return true - is the very triangle, false - is not
365 */
366bool BoundaryTriangleSet::IsPresentTupel(const BoundaryPointSet * const Points[3]) const
367{
368 //Info FunctionInfo(__func__);
369 LOG(5, "DEBUG: Checking " << *Points[0] << "," << *Points[1] << "," << *Points[2]
370 << " against " << *this); //*endpoints[0] << "," << *endpoints[1] << "," << *endpoints[2] << ".");
371 return (((endpoints[0] == Points[0]) || (endpoints[0] == Points[1]) || (endpoints[0] == Points[2]))
372 && ((endpoints[1] == Points[0]) || (endpoints[1] == Points[1]) || (endpoints[1] == Points[2]))
373 && ((endpoints[2] == Points[0]) || (endpoints[2] == Points[1]) || (endpoints[2] == Points[2])
374
375 ));
376}
377;
378
379/** Checks whether three given \a *Points coincide with triangle's endpoints.
380 * \param *Points[3] pointer to BoundaryPointSet
381 * \return true - is the very triangle, false - is not
382 */
383bool BoundaryTriangleSet::IsPresentTupel(const BoundaryTriangleSet * const T) const
384{
385 //Info FunctionInfo(__func__);
386 return (((endpoints[0] == T->endpoints[0]) || (endpoints[0] == T->endpoints[1]) || (endpoints[0] == T->endpoints[2])) && ((endpoints[1] == T->endpoints[0]) || (endpoints[1] == T->endpoints[1]) || (endpoints[1] == T->endpoints[2])) && ((endpoints[2] == T->endpoints[0]) || (endpoints[2] == T->endpoints[1]) || (endpoints[2] == T->endpoints[2])
387
388 ));
389}
390;
391
392/** Checks whether a given point is inside the plane of the triangle and inside the
393 * bounds defined by its BoundaryLineSet's.
394 *
395 * @param point point to check
396 * @return true - point is inside place and inside all BoundaryLine's
397 */
398bool BoundaryTriangleSet::IsInsideTriangle(const Vector &point) const
399{
400 Info FunctionInfo(__func__);
401
402 // check if it's inside the plane
403 try {
404 Plane trianglePlane(
405 endpoints[0]->node->getPosition(),
406 endpoints[1]->node->getPosition(),
407 endpoints[2]->node->getPosition());
408 if (!trianglePlane.isContained(point)) {
409 LOG(1, "INFO: Point " << point << " is not inside plane " << trianglePlane << " by "
410 << trianglePlane.distance(point) << ".");
411 return false;
412 }
413 } catch(LinearDependenceException) {
414 // triangle is degenerated, it's just a line (i.e. one endpoint is right in between two others
415 for (size_t i = 0; i < NDIM; ++i) {
416 try {
417 Line l = makeLineThrough(
418 lines[i]->endpoints[0]->node->getPosition(),
419 lines[i]->endpoints[1]->node->getPosition());
420 if (l.isContained(GetThirdEndpoint(lines[i])->node->getPosition())) {
421 // we have the largest of the three lines
422 LOG(1, "INFO: Linear-dependent case where point " << point << " is on line " << l << ".");
423 return (l.isContained(point));
424 }
425 } catch(ZeroVectorException) {
426 // two points actually coincide
427 try {
428 Line l = makeLineThrough(
429 lines[i]->endpoints[0]->node->getPosition(),
430 GetThirdEndpoint(lines[i])->node->getPosition());
431 LOG(1, "INFO: Degenerated case where point " << point << " is on line " << l << ".");
432 return (l.isContained(point));
433 } catch(ZeroVectorException) {
434 // all three points coincide
435 if (point.DistanceSquared(lines[i]->endpoints[0]->node->getPosition()) < MYEPSILON) {
436 LOG(1, "INFO: Full-Degenerated case where point " << point << " is on three endpoints "
437 << lines[i]->endpoints[0]->node->getPosition() << ".");
438 return true;
439 }
440 else return false;
441 }
442 }
443 }
444 }
445
446 // check whether it lies on the correct side as given by third endpoint for
447 // each BoundaryLine.
448 // NOTE: we assume here that endpoints are linear independent, as the case
449 // has been caught before already extensively
450 for (size_t i = 0; i < NDIM; ++i) {
451 Line l = makeLineThrough(
452 lines[i]->endpoints[0]->node->getPosition(),
453 lines[i]->endpoints[1]->node->getPosition());
454 Vector onLine( l.getClosestPoint(point) );
455 LOG(1, "INFO: Closest point on boundary line is " << onLine << ".");
456 Vector inTriangleDirection( GetThirdEndpoint(lines[i])->node->getPosition() - onLine );
457 Vector inPointDirection(point - onLine);
458 if ((inTriangleDirection.NormSquared() > MYEPSILON) && (inPointDirection.NormSquared() > MYEPSILON))
459 if (inTriangleDirection.ScalarProduct(inPointDirection) < -MYEPSILON)
460 return false;
461 }
462
463 return true;
464}
465
466
467/** Returns the endpoint which is not contained in the given \a *line.
468 * \param *line baseline defining two endpoints
469 * \return pointer third endpoint or NULL if line does not belong to triangle.
470 */
471class BoundaryPointSet *BoundaryTriangleSet::GetThirdEndpoint(const BoundaryLineSet * const line) const
472{
473 //Info FunctionInfo(__func__);
474 // sanity check
475 if (!ContainsBoundaryLine(line))
476 return NULL;
477 for (int i = 0; i < 3; i++)
478 if (!line->ContainsBoundaryPoint(endpoints[i]))
479 return endpoints[i];
480 // actually, that' impossible :)
481 return NULL;
482}
483;
484
485/** Returns the baseline which does not contain the given boundary point \a *point.
486 * \param *point endpoint which is neither endpoint of the desired line
487 * \return pointer to desired third baseline
488 */
489class BoundaryLineSet *BoundaryTriangleSet::GetThirdLine(const BoundaryPointSet * const point) const
490{
491 //Info FunctionInfo(__func__);
492 // sanity check
493 if (!ContainsBoundaryPoint(point))
494 return NULL;
495 for (int i = 0; i < 3; i++)
496 if (!lines[i]->ContainsBoundaryPoint(point))
497 return lines[i];
498 // actually, that' impossible :)
499 return NULL;
500}
501;
502
503/** Calculates the center point of the triangle.
504 * Is third of the sum of all endpoints.
505 * \param *center central point on return.
506 */
507void BoundaryTriangleSet::GetCenter(Vector & center) const
508{
509 //Info FunctionInfo(__func__);
510 center.Zero();
511 for (int i = 0; i < 3; i++)
512 (center) += (endpoints[i]->node->getPosition());
513 center.Scale(1. / 3.);
514 LOG(4, "DEBUG: Center of BoundaryTriangleSet is at " << center << ".");
515}
516
517/**
518 * gets the Plane defined by the three triangle Basepoints
519 */
520Plane BoundaryTriangleSet::getPlane() const{
521 ASSERT(endpoints[0] && endpoints[1] && endpoints[2], "Triangle not fully defined");
522
523 return Plane(endpoints[0]->node->getPosition(),
524 endpoints[1]->node->getPosition(),
525 endpoints[2]->node->getPosition());
526}
527
528Vector BoundaryTriangleSet::getEndpoint(int i) const{
529 ASSERT(i>=0 && i<3,"Index of Endpoint out of Range");
530
531 return endpoints[i]->node->getPosition();
532}
533
534string BoundaryTriangleSet::getEndpointName(int i) const{
535 ASSERT(i>=0 && i<3,"Index of Endpoint out of Range");
536
537 return endpoints[i]->node->getName();
538}
539
540/** output operator for BoundaryTriangleSet.
541 * \param &ost output stream
542 * \param &a boundary triangle
543 */
544ostream &operator <<(ostream &ost, const BoundaryTriangleSet &a)
545{
546 ost << "[" << a.Nr << "|" << a.getEndpointName(0) << "," << a.getEndpointName(1) << "," << a.getEndpointName(2) << "]";
547 // ost << "[" << a.Nr << "|" << a.endpoints[0]->node->Name << " at " << *a.endpoints[0]->node->node << ","
548 // << a.endpoints[1]->node->Name << " at " << *a.endpoints[1]->node->node << "," << a.endpoints[2]->node->Name << " at " << *a.endpoints[2]->node->node << "]";
549 return ost;
550}
551;
552
Note: See TracBrowser for help on using the repository browser.