r204: *** empty log message ***
[ctsim.git] / libctsim / phantom.cpp
1 /*****************************************************************************
2 ** FILE IDENTIFICATION
3 ** 
4 **     Name:                   phm.cpp
5 **     Purpose:                Routines for phantom objects
6 **     Progammer:              Kevin Rosenberg
7 **     Date Started:           Aug 1984
8 **
9 **  This is part of the CTSim program
10 **  Copyright (C) 1983-2000 Kevin Rosenberg
11 **
12 **  $Id: phantom.cpp,v 1.19 2000/11/09 00:12:25 kevin Exp $
13 **
14 **  This program is free software; you can redistribute it and/or modify
15 **  it under the terms of the GNU General Public License (version 2) as
16 **  published by the Free Software Foundation.
17 **
18 **  This program is distributed in the hope that it will be useful,
19 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
20 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 **  GNU General Public License for more details.
22 **
23 **  You should have received a copy of the GNU General Public License
24 **  along with this program; if not, write to the Free Software
25 **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26 ******************************************************************************/
27
28 #include "ct.h"
29
30
31 const int Phantom::PHM_INVALID = -1;
32 const int Phantom::PHM_HERMAN = 0;
33 const int Phantom::PHM_B_HERMAN = 1;
34 const int Phantom::PHM_SHEPP_LOGAN = 2;
35 const int Phantom::PHM_B_SHEPP_LOGAN = 3;
36 const int Phantom::PHM_UNITPULSE = 4;
37
38 const char* Phantom::s_aszPhantomName[] = 
39 {
40   {"herman"},
41   {"herman-b"},
42   {"shepp-logan"},
43   {"shepp-logan-b"},
44   {"unitpulse"},
45 };
46
47 const char* Phantom::s_aszPhantomTitle[] = 
48 {
49   {"Herman Head"},
50   {"Herman Head (Bordered)"},
51   {"Shepp-Logan"},
52   {"Shepp-Logan (Bordered)"},
53   {"Unit Pulse"},
54 };
55
56 const int Phantom::s_iPhantomCount = sizeof(s_aszPhantomName) / sizeof(const char*);
57
58
59 // CLASS IDENTIFICATION
60 //   Phantom
61 //
62
63 Phantom::Phantom ()
64 {
65   init ();
66 }
67
68
69 Phantom::Phantom (const char* const phmName)
70 {
71   init ();
72   createFromPhantom (phmName);
73 }
74
75 void 
76 Phantom::init ()
77 {
78   m_nPElem = 0;
79   m_xmin = 1E30;
80   m_xmax = -1E30;
81   m_ymin = 1E30;
82   m_ymax = -1E30;
83   m_diameter = 0;
84   m_composition = P_PELEMS;
85   m_fail = false;
86   m_id = PHM_INVALID;
87 }
88
89 Phantom::~Phantom ()
90 {
91   for (PElemIterator i = m_listPElem.begin(); i != m_listPElem.end(); i++) {
92     delete *i;
93   }
94 }
95
96
97 const char*
98 Phantom::convertPhantomIDToName (int phmID)
99 {
100   static const char *name = "";
101
102   if (phmID >= 0 && phmID < s_iPhantomCount)
103       return (s_aszPhantomName[phmID]);
104
105   return (name);
106 }
107
108 const char*
109 Phantom::convertPhantomIDToTitle (int phmID)
110 {
111   static const char *title = "";
112
113   if (phmID >= 0 && phmID < s_iPhantomCount)
114       return (s_aszPhantomName[phmID]);
115
116   return (title);
117 }
118       
119 int
120 Phantom::convertNameToPhantomID (const char* const phmName) 
121 {
122   int id = PHM_INVALID;
123
124   for (int i = 0; i < s_iPhantomCount; i++)
125       if (strcasecmp (phmName, s_aszPhantomName[i]) == 0) {
126           id = i;
127           break;
128       }
129
130   return (id);
131 }
132   
133
134 bool
135 Phantom::createFromPhantom (const char* const phmName)
136 {
137   int phmid = convertNameToPhantomID (phmName);
138   if (phmid == PHM_INVALID) {
139     m_fail = true;
140     m_failMessage = "Invalid phantom name ";
141     m_failMessage += phmName;
142     return false;
143   }
144
145   m_name = phmName;
146   createFromPhantom (phmid);
147   return true;
148 }
149
150 bool
151 Phantom::createFromPhantom (const int phmid)
152 {
153   switch (phmid) 
154     {
155     case PHM_HERMAN:
156       addStdHerman();
157       break;
158     case PHM_B_HERMAN:
159       addStdHermanBordered();
160       break;
161     case PHM_SHEPP_LOGAN:
162       addStdSheppLogan();
163       break;
164     case PHM_B_SHEPP_LOGAN:
165       addStdSheppLoganBordered();
166       break;
167     case PHM_UNITPULSE:
168       m_composition = P_UNIT_PULSE;
169       addPElem ("rectangle", 0., 0., 100., 100., 0., 0.);     // outline 
170       addPElem ("ellipse", 0., 0., 1., 1., 0., 1.);           // pulse 
171       break;
172     default:
173       m_fail = true;
174       m_failMessage = "Illegal phantom id ";
175       m_failMessage += phmid;
176       return false;
177     }
178
179   m_id = phmid;
180
181   return true;
182 }
183
184
185 /* METHOD IDENTIFICATION
186  *   createFromFile          Add PhantomElements from file
187  *
188  * SYNOPSIS
189  *   createFromFile (filename)
190  *
191  * RETURNS
192  *   true if pelem were added
193  *   false if an pelem not added
194  */
195
196 bool
197 Phantom::createFromFile (const char* const fname)
198 {
199   bool bGoodFile = true;
200   FILE *fp;
201
202   if ((fp = fopen (fname, "r")) == NULL)
203     return (false);
204
205   m_name = fname;
206
207   while (1) {
208     double cx, cy, u, v, rot, dens;
209     char pelemtype[80];
210
211     int status = fscanf (fp, "%79s %lf %lf %lf %lf %lf %lf", pelemtype, &cx, &cy, &u, &v, &rot, &dens);
212     
213     if (status == static_cast<int>(EOF)) 
214       break;
215     else if (status != 7) {
216       sys_error (ERR_WARNING, "Insufficient fields reading phantom file %s [Phantom::createFromFile]", fname);
217       bGoodFile = false;
218     }
219     addPElem (pelemtype, cx, cy, u, v, rot, dens);
220   }
221   
222   fclose (fp);
223
224   return (bGoodFile);
225 }
226
227
228 /* NAME
229  *   addPElem           Add pelem
230  *
231  * SYNOPSIS
232  *   addPElem (type, cx, cy, u, v, rot, atten)
233  *   char *type         type of pelem (box, ellipse, etc)
234  *   double cx, cy      pelem center
235  *   double u,v         pelem size
236  *   double rot         rotation angle of pelem (in degrees)
237  *   double atten       x-ray attenuation cooefficient
238  */
239
240 void 
241 Phantom::addPElem (const char *type, const double cx, const double cy, const double u, const double v, const double rot, const double atten)
242 {
243   PhantomElement *pelem = new PhantomElement (type, cx, cy, u, v, rot, atten);
244
245   m_listPElem.push_front (pelem);
246
247   // update phantom limits
248   if (m_xmin > pelem->xmin())    m_xmin = pelem->xmin();
249   if (m_xmax < pelem->xmax())    m_xmax = pelem->xmax();
250   if (m_ymin > pelem->ymin())    m_ymin = pelem->ymin();
251   if (m_ymax < pelem->ymax())    m_ymax = pelem->ymax();
252
253   if (m_diameter < pelem->diameter())
254     m_diameter = pelem->diameter();
255
256   //  m_diameter = lineLength(m_xmin, m_ymin, m_xmax, m_ymax);
257
258   m_nPElem++;
259 }
260
261
262 /*----------------------------------------------------------------------*/
263 /*                      Input-Output Routines                           */
264 /*----------------------------------------------------------------------*/
265
266
267 /* NAME
268  *   print                              Print vertices of Phantom pelems
269  *
270  * SYNOPSIS
271  *   print (phm)
272  */
273
274 void 
275 Phantom::print () const
276 {
277   printf("PRINTING Phantom\n\n");
278   printf("number of pelems in Phantom = %d\n", m_nPElem);
279   printf("limits: xmin=%8.2g  ymin=%8.2g  xmax=%8.2g  ymax=%8.2g\n",
280          m_xmin, m_ymin, m_xmax, m_ymax);
281   
282   for (PElemIterator i = m_listPElem.begin(); i != m_listPElem.end(); i++) {
283       printf("PELEM:\n");
284       printf("# pts=%3d atten = %7.4f   rot = %7.2f (deg)\n",
285              (*i)->nOutlinePoints(), (*i)->atten(), convertRadiansToDegrees ((*i)->rot()));
286     
287     printf("xmin=%7.3g  ymin=%7.3g  xmax=%7.3g  ymax=%7.3g\n",
288            (*i)->xmin(), (*i)->ymin(), (*i)->xmax(), (*i)->ymax());
289     
290     //    for (int i = 0; i < m_nPoints; i++)
291     //      printf("\t%8.3g    %8.3g\n", i->xOutline()[i], i->yOutline()[i]);
292   }
293 }
294
295
296 /* NAME
297  *   show               Show vector outline of Phantom to user
298  *
299  * SYNOPSIS
300  *   show (pic)
301  */
302
303 #ifdef HAVE_SGP
304 void 
305 Phantom::show () const
306 {
307   SGPDriver driverSGP ("Phantom Show");
308   SGP sgp (driverSGP);
309
310   show (sgp);
311
312   cout << "Press return to continue";
313   cio_kb_getc();
314 }
315
316 void 
317 Phantom::show (SGP& sgp) const
318 {
319   double wsize = m_xmax - m_xmin;
320   if ((m_ymax - m_ymin) > wsize) 
321       wsize = m_ymax - m_ymin;
322   wsize *= 1.01;
323   double halfWindow = wsize / 2;
324
325   double xcent = m_xmin + (m_xmax - m_xmin) / 2;
326   double ycent = m_ymin + (m_ymax - m_ymin) / 2;
327
328   sgp.setWindow (xcent - halfWindow, ycent - halfWindow, xcent + halfWindow, ycent + halfWindow);
329
330   draw (sgp);
331 }
332 #endif
333
334
335 /* NAME
336  *   draw               Draw vector outline of Phantom
337  *
338  * SYNOPSIS
339  *   draw ()
340  */
341
342 #ifdef HAVE_SGP
343 void 
344 Phantom::draw (SGP& sgp) const
345 {
346   for (PElemIterator i = m_listPElem.begin(); i != m_listPElem.end(); i++)
347     sgp.polylineAbs ((*i)->xOutline(), (*i)->yOutline(), (*i)->nOutlinePoints());
348 }
349 #endif
350
351
352 /* NAME
353  *   addStdSheppLogan   Make head phantom of Shepp-Logan
354  *
355  * REFERENCES
356  *   S. W. Rowland, "Computer Implementation of Image Reconstruction
357  *      Formulas", in "Image Reconstruction from Projections: Implementation
358  *      and Applications", edited by G. T. Herman, 1978.
359  */
360
361 void 
362 Phantom::addStdSheppLogan ()
363 {
364   addPElem ("ellipse",  0.0000,  0.0000, 0.6900,  0.9200,   0.0,  1.00);
365   addPElem ("ellipse",  0.0000, -0.0184, 0.6624,  0.8740,   0.0, -0.98);
366   addPElem ("ellipse",  0.2200,  0.0000, 0.1100,  0.3100, -18.0, -0.02);
367   addPElem ("ellipse", -0.2200,  0.0000, 0.1600,  0.4100,  18.0, -0.02);
368   addPElem ("ellipse",  0.0000,  0.3500, 0.2100,  0.2500,   0.0,  0.01);
369   addPElem ("ellipse",  0.0000,  0.1000, 0.0460,  0.0460,   0.0,  0.01);
370   addPElem ("ellipse",  0.0000, -0.1000, 0.0460,  0.0460,   0.0,  0.01);
371   addPElem ("ellipse", -0.0800, -0.6050, 0.0460,  0.0230,   0.0,  0.01);
372   addPElem ("ellipse",  0.0000, -0.6050, 0.0230,  0.0230,   0.0,  0.01);
373   addPElem ("ellipse",  0.0600, -0.6050, 0.0230,  0.0230,   0.0,  0.01);
374   addPElem ("ellipse",  0.5538, -0.3858, 0.0330,  0.2060, -18.0,  0.03);
375 }
376
377 void 
378 Phantom::addStdSheppLoganBordered ()
379 {
380   addStdSheppLogan ();
381   addPElem ("rectangle", 0.000, 0.0000, 0.8600, 1.150, 0.0, 0.00);
382 }
383
384 /* NAME
385  *   addStdHerman                       Standard head phantom of G. T. Herman
386  *
387  * REFERENCES
388  *   G. T. Herman, "Image Reconstructions from Projections:  The Fundementals
389  *      of Computed Tomography", 1979.
390  */
391
392 void 
393 Phantom::addStdHerman ()
394 {
395   addPElem ("ellipse",  0.000,  1.50,  0.375, 0.3000,  90.00, -0.003);
396   addPElem ("ellipse",  0.675, -0.75,  0.225, 0.1500, 140.00,  0.010);
397   addPElem ("ellipse",  0.750,  1.50,  0.375, 0.2250,  50.00,  0.003);
398   addPElem ("segment",  1.375, -7.50,  1.100, 0.6250,  19.20, -0.204);
399   addPElem ("segment",  1.375, -7.50,  1.100, 4.3200,  19.21,  0.204);
400   addPElem ("segment",  0.000, -2.25,  1.125, 0.3750,   0.00, -0.003);
401   addPElem ("segment",  0.000, -2.25,  1.125, 3.0000,   0.00,  0.003);
402   addPElem ("segment", -1.000,  3.75,  1.000, 0.5000, 135.00, -0.003);
403   addPElem ("segment", -1.000,  3.75,  1.000, 3.0000, 135.00,  0.003);
404   addPElem ("segment",  1.000,  3.75,  1.000, 0.5000, 225.00, -0.003);
405   addPElem ("segment",  1.000,  3.75,  1.000, 3.0000, 225.00,  0.003);
406   addPElem ("triangle", 5.025,  3.75,  1.125, 0.5000, 110.75,  0.206);
407   addPElem ("triangle",-5.025,  3.75,  1.125, 0.9000,-110.75,  0.206);
408   addPElem ("ellipse",  0.000,  0.00,  8.625, 6.4687,  90.00,  0.416);
409   addPElem ("ellipse",  0.000,  0.00,  7.875, 5.7187,  90.00, -0.206);
410 }
411
412 void
413 Phantom::addStdHermanBordered ()
414 {
415   addStdHerman();
416   addPElem ("rectangle",  0.000, 0.00, 10.780, 8.110,  90.00, 0.000);
417 }
418
419
420 /* NAME
421  *    convertToImagefile                Make image array from Phantom
422  *
423  * SYNOPSIS
424  *    pic_to_imagefile (pic, im, nsample)
425  *    Phantom& pic              Phantom definitions
426  *    ImageFile  *im            Computed pixel array
427  *    int nsample               Number of samples along each axis for each pixel
428  *                              (total samples per pixel = nsample * nsample)
429  */
430
431 void
432 Phantom::convertToImagefile (ImageFile& im, const int in_nsample, const int trace) const
433 {
434   convertToImagefile (im, in_nsample, trace, 0, im.nx(), true);
435 }
436
437 void 
438 Phantom::convertToImagefile (ImageFile& im, const int in_nsample, const int trace, const int colStart, const int colCount, bool bStoreAtColumnPos) const
439 {
440   int nx = im.nx();
441   int ny = im.ny();
442   if (nx < 2 || ny < 2)
443       return;
444
445   int nsample = in_nsample;
446   if (nsample < 1)  
447     nsample = 1;
448
449   double dx = m_xmax - m_xmin;
450   double dy = m_ymax - m_ymin;
451   double xcent = m_xmin + dx / 2;
452   double ycent = m_ymin + dy / 2;
453   double phmlen = (dx > dy ? dx : dy);
454
455   double phmradius = phmlen / 2;
456
457   double xmin = xcent - phmradius;
458   double xmax = xcent + phmradius;
459   double ymin = ycent - phmradius;
460   double ymax = ycent + phmradius;
461
462   // Each pixel holds the average of the intensity of the cell with (ix,iy) at the center of the pixel
463   // Set major increments so that the last cell v[nx-1][ny-1] will start at xmax - xinc, ymax - yinc).
464   // Set minor increments so that sample points are centered in cell
465
466   double xinc = (xmax - xmin) / nx;
467   double yinc = (ymax - ymin) / ny;
468
469   double kxinc = xinc / nsample;                /* interval between samples */
470   double kyinc = yinc / nsample;
471   double kxofs = kxinc / 2;             /* offset of 1st point */
472   double kyofs = kyinc / 2;
473
474   im.setAxisExtent (xmin, xmax, ymin, ymax);
475   im.setAxisIncrement (xinc, yinc);
476
477   ImageFileArray v = im.getArray();
478
479   for (int ix = 0; ix < colCount; ix++)
480     for (int iy = 0; iy < ny; iy++) {
481       int iColStore = ix;
482       if (bStoreAtColumnPos)
483         iColStore += colStart;
484       v[iColStore][iy] = 0;
485     }
486
487   double x_start = xmin + (colStart * xinc);
488   for (PElemConstIterator pelem = m_listPElem.begin(); pelem != m_listPElem.end(); pelem++) {
489       const PhantomElement& rPElem = **pelem;
490       double x, y, xi, yi;
491       int ix, iy, kx, ky;
492       for (ix = 0, x = x_start; ix < colCount; ix++, x += xinc) {
493         int iColStore = ix;
494         if (bStoreAtColumnPos)
495           iColStore += colStart;
496         for (iy = 0, y = ymin; iy < ny; iy++, y += yinc) {
497           for (kx = 0, xi = x + kxofs; kx < nsample; kx++, xi += kxinc) {
498             for (ky = 0, yi = y + kyofs; ky < nsample; ky++, yi += kyinc)
499               if (rPElem.isPointInside (xi, yi, PHM_COORD) == TRUE)
500                 v[iColStore][iy] += rPElem.atten();
501               } // for kx
502           } /* for iy */
503       }  /* for ix */
504   }  /* for pelem */
505   
506
507   if (nsample > 1) {
508     double factor = 1.0 / (nsample * nsample);
509
510     for (int ix = 0; ix < colCount; ix++)
511       for (int iy = 0; iy < ny; iy++)
512         v[ix][iy] *= factor;
513   }
514 }
515
516 ////////////////////////////////////////////////////////////////////////////////////////////////////////
517 // CLASS IDENTIFICATION
518 //
519 //      PhantomElement
520 //
521 // PURPOSE
522 //
523 ////////////////////////////////////////////////////////////////////////////////////////////////////////
524
525
526 PhantomElement::PhantomElement (const char *type, const double cx, const double cy, const double u, const double v, const double rot, const double atten)
527     : m_cx(cx), m_cy(cy), m_u(u), m_v(v), m_atten(atten), m_nPoints(0), m_xOutline(0), m_yOutline(0)
528 {
529   m_rot = convertDegreesToRadians (rot);   // convert angle to radians
530
531   m_type = convertNameToType (type);
532
533   makeTransformMatrices ();     // calc transform matrices between phantom and normalized phantomelement
534   makeVectorOutline ();         // calculate vector outline of pelem 
535
536   // Find maximum diameter of Object
537   double r2Max = 0;
538   for (int i = 0; i < m_nPoints; i++) {
539     double r2 = (m_xOutline[i] * m_xOutline[i]) + (m_yOutline[i] * m_yOutline[i]);
540     if (r2 > r2Max)
541       r2Max = r2;
542   }
543   m_diameter = 2 * sqrt( r2Max );
544
545   m_rectLimits[0] = m_xmin;   m_rectLimits[1] = m_ymin;
546   m_rectLimits[2] = m_xmax;   m_rectLimits[3] = m_ymax;
547 }
548
549
550 PhantomElement::~PhantomElement ()
551 {
552     delete m_xOutline;
553     delete m_yOutline;
554 }
555
556 PhmElemType
557 PhantomElement::convertNameToType (const char* const typeName)
558 {
559     PhmElemType type = PELEM_INVALID;
560
561     if (strcasecmp (typeName, "rectangle") == 0)
562         type = PELEM_RECTANGLE;
563     else if (strcasecmp (typeName, "triangle") == 0)
564         type = PELEM_TRIANGLE;
565     else if (strcasecmp (typeName, "ellipse") == 0)
566         type = PELEM_ELLIPSE;
567     else if (strcasecmp (typeName, "sector") == 0)
568         type = PELEM_SECTOR;
569     else if (strcasecmp (typeName, "segment") == 0)
570       type = PELEM_SEGMENT;
571     else
572         sys_error (ERR_WARNING, "Unknown PhantomElement type %s [PhantomElement::PhantomElement]", type);
573
574     return (type);
575 }
576
577 void 
578 PhantomElement::makeTransformMatrices ()
579 {
580   GRFMTX_2D temp;
581
582   // To map normalized Pelem coords to world Phantom 
583   //     scale by (u, v)                                       
584   //     rotate by rot                                  
585   //     translate by (cx, cy)                         
586
587   scale_mtx2 (m_xformObjToPhm, m_u, m_v);
588   rot_mtx2  (temp, m_rot);
589   mult_mtx2 (m_xformObjToPhm, temp, m_xformObjToPhm);
590   xlat_mtx2 (temp, m_cx, m_cy);
591   mult_mtx2 (m_xformObjToPhm, temp, m_xformObjToPhm);
592
593   // to map world Phantom coodinates to normalized PElem coords
594   //     translate by (-cx, -cy)
595   //     rotate by -rot
596   //     scale by (1/u, 1/v)
597
598   xlat_mtx2 (m_xformPhmToObj, -m_cx, -m_cy);
599   rot_mtx2  (temp, -m_rot);
600   mult_mtx2 (m_xformPhmToObj, temp, m_xformPhmToObj);
601   scale_mtx2 (temp, 1 / m_u, 1 / m_v);
602   mult_mtx2 (m_xformPhmToObj, temp, m_xformPhmToObj);
603 }
604
605
606 /* NAME
607  *   pelem_make_points          INTERNAL routine to calculate point array for an pelem
608  *
609  * SYNOPSIS
610  *   makepelempts (pelem)
611  *   PELEM *pelem       pelem whose points we are calculating
612  *
613  * NOTES
614  *   Called by phm_add_pelem()
615  */
616
617 void
618 PhantomElement::makeVectorOutline ()
619 {
620   double radius, theta, start, stop;
621   double xfact, yfact;
622   int cpts;
623
624   m_nPoints = 0;
625   switch (m_type) {
626   case PELEM_RECTANGLE:
627     m_nPoints = 5;
628     m_xOutline = new double [m_nPoints];
629     m_yOutline = new double [m_nPoints];
630     m_xOutline[0] =-m_u;        m_yOutline[0] =-m_v;
631     m_xOutline[1] = m_u;        m_yOutline[1] =-m_v;
632     m_xOutline[2] = m_u;        m_yOutline[2] = m_v;
633     m_xOutline[3] =-m_u;        m_yOutline[3] = m_v;
634     m_xOutline[4] =-m_u;        m_yOutline[4] =-m_v;
635     break;
636   case PELEM_TRIANGLE:
637     m_nPoints = 4;
638     m_xOutline = new double [m_nPoints];
639     m_yOutline = new double [m_nPoints];
640     m_xOutline[0] =-m_u;        m_yOutline[0] = 0.0;
641     m_xOutline[1] = m_u;        m_yOutline[1] = 0.0;
642     m_xOutline[2] = 0.0;        m_yOutline[2] = m_v;
643     m_xOutline[3] =-m_u;        m_yOutline[3] = 0.0;
644     break;
645   case PELEM_ELLIPSE:
646     cpts = numCirclePoints (TWOPI);
647     m_nPoints = cpts;
648     m_xOutline = new double [m_nPoints];
649     m_yOutline = new double [m_nPoints];
650     calcEllipsePoints (m_xOutline, m_yOutline, cpts, m_u, m_v);
651     break;
652   case PELEM_SECTOR:
653     radius = sqrt(m_u * m_u + m_v * m_v);
654     theta = atan(m_u / m_v);            // angle with y-axis 
655     start = 3.0 * HALFPI - theta;
656     stop  = 3.0 * HALFPI + theta;
657     cpts = numCirclePoints (stop - start);
658     m_nPoints = 3 + cpts;
659     m_xOutline = new double [m_nPoints];
660     m_yOutline = new double [m_nPoints];
661     
662     m_xOutline[0] = 0.0;                m_yOutline[0] = m_v;
663     m_xOutline[1] =-m_u;                m_yOutline[1] = 0.0;
664     calcArcPoints (&m_xOutline[2], &m_yOutline[2], cpts, 0.0, m_v, radius, start, stop);
665     m_xOutline[cpts + 2] = 0.0;
666     m_yOutline[cpts + 2] = m_v;
667     break;
668   case PELEM_SEGMENT:
669     radius = sqrt(m_u * m_u + m_v * m_v);
670     theta = atan (m_u / m_v);           // angle with y-axis 
671     start = 3.0 * HALFPI - theta;
672     stop  = 3.0 * HALFPI + theta;
673     
674     cpts = numCirclePoints (stop - start);
675     m_nPoints = cpts + 1;
676     m_xOutline = new double [m_nPoints];
677     m_yOutline = new double [m_nPoints];
678     
679     calcArcPoints (m_xOutline, m_yOutline, cpts, 0.0, m_v, radius, start, stop);
680     m_xOutline[cpts] = -m_u;
681     m_yOutline[cpts] = 0.0;
682     break;
683   default:
684     sys_error(ERR_WARNING, "illegal pelem type %d [makeVectorOutline]", m_type);
685     return;
686   }
687   
688   rotate2d (m_xOutline, m_yOutline, m_nPoints, m_rot);
689   xlat2d (m_xOutline, m_yOutline, m_nPoints, m_cx, m_cy);
690   
691   minmax_array (m_xOutline, m_nPoints, m_xmin, m_xmax);
692   minmax_array (m_yOutline, m_nPoints, m_ymin, m_ymax);
693   
694   // increase pelem extent by SCALE_PELEM_EXTENT to eliminate chance of
695   //   missing actual pelem maximum due to polygonal sampling 
696
697   xfact = (m_xmax - m_xmin) * SCALE_PELEM_EXTENT;
698   yfact = (m_ymax - m_ymin) * SCALE_PELEM_EXTENT;
699
700   m_xmin -= xfact;
701   m_ymin -= yfact;
702   m_xmax += xfact;
703   m_ymax += yfact;
704 }
705
706
707 /* NAME
708  *   calc_arc                   Calculate outline of a arc of a circle
709  *
710  * SYNOPSIS
711  *   calc_arc (x, y, xcent, ycent, pts, r, start, stop)
712  *   double x[], y[];           Array of points
713  *   int pts                    Number of points in array
714  *   double xcent, ycent        Center of cirlce
715  *   double r                   Radius of circle
716  *   double start, stop         Beginning & ending angles
717  */
718
719 void 
720 PhantomElement::calcArcPoints (double x[], double y[], const int pts, const double xcent, const double ycent, const double r, const double start, const double stop)
721 {
722     if (r <= 0.0)
723         sys_error (ERR_WARNING, "negative or zero radius in calc_arc()");
724
725     double theta = (stop - start) / (pts - 1);  // angle incr. between points 
726     double c = cos(theta);
727     double s = sin(theta);
728   
729     x[0] = r * cos (start) + xcent;
730     y[0] = r * sin (start) + ycent;
731
732     double xp = x[0] - xcent;
733     double yp = y[0] - ycent;
734     for (int i = 1; i < pts; i++) {
735         double xc = c * xp - s * yp;
736         double yc = s * xp + c * yp;
737         x[i] = xc + xcent;
738         y[i] = yc + ycent;
739         xp = xc;  yp = yc;
740     }
741 }
742
743
744 // NAME
745 //   PhantomElement::calcEllipsePoints    Calculate outline of a ellipse
746 //
747 // SYNOPSIS
748 //   calcEllipsePoints ()
749 //
750
751
752 void 
753 PhantomElement::calcEllipsePoints (double x[], double y[], const int pts, const double u, const double v)
754 {
755     calcArcPoints (x, y, m_nPoints, 0.0, 0.0, 1.0, 0.0, TWOPI);   // make a unit circle 
756     scale2d (x, y, m_nPoints, m_u, m_v);                             // scale to ellipse 
757 }
758
759
760 /* NAME
761  *   circle_pts         Calculate number of points to use for circle segment
762  *
763  * SYNOPSIS
764  *   n = circle_pts (theta)
765  *   int n              Number of points to use for arc
766  *   double theta       Length of arc in radians
767  */
768
769 int 
770 PhantomElement::numCirclePoints (double theta)
771 {
772     theta = clamp (theta, 0., TWOPI);
773
774     return static_cast<int> (POINTS_PER_CIRCLE * theta / TWOPI + 1.5);
775 }
776
777
778 bool
779 PhantomElement::clipLineWorldCoords (double& x1, double& y1, double& x2, double &y2) const
780 {
781   /* check if ray is outside of pelem extents */
782   double cx1 = x1, cy1 = y1, cx2 = x2, cy2 = y2;
783   if (! clip_rect (cx1, cy1, cx2, cy2, m_rectLimits))
784     return false;
785         
786   // convert phantom coordinates to pelem coordinates 
787   xform_mtx2 (m_xformPhmToObj, x1, y1);
788   xform_mtx2 (m_xformPhmToObj, x2, y2);
789         
790   if (! clipLineNormalizedCoords (x1, y1, x2, y2))
791     return false;
792
793   // convert standard pelem coordinates back to phantom coordinates 
794   xform_mtx2 (m_xformObjToPhm, x1, y1);
795   xform_mtx2 (m_xformObjToPhm, x2, y2);
796
797   return true;
798 }
799
800
801 /* NAME
802  *   pelem_clip_line                    Clip pelem against an arbitrary line
803  *
804  * SYNOPSIS
805  *   pelem_clip_line (pelem, x1, y1, x2, y2)
806  *   PhantomElement& pelem;             Pelem to be clipped
807  *   double *x1, *y1, *x2, *y2  Endpoints of line to be clipped
808  *
809  * RETURNS
810  *   true   if line passes through pelem
811  *              (x1, y1, x2, y2 hold coordinates of new line)
812  *   false  if line do not pass through pelem
813  *              (x1, y1, x2, y2 are undefined)
814  */
815
816 bool
817 PhantomElement::clipLineNormalizedCoords (double& x1, double& y1, double& x2, double& y2) const
818 {
819   bool accept = false;
820
821   switch (m_type) {
822   case PELEM_RECTANGLE:
823     double rect[4];
824     rect[0] = -1.0;  rect[1] = -1.0;
825     rect[2] = 1.0;  rect[3] = 1.0;
826     accept = clip_rect (x1, y1, x2, y2, rect);
827     break;
828   case PELEM_ELLIPSE:
829     accept = clip_circle (x1, y1, x2, y2, 0.0, 0.0, 1.0, 0.0, 0.0);
830     break;
831   case PELEM_TRIANGLE:
832     accept = clip_triangle (x1, y1, x2, y2, 1.0, 1.0, true);
833     break;
834   case PELEM_SEGMENT:
835     accept = clip_segment (x1, y1, x2, y2, m_u, m_v);
836     break;
837   case PELEM_SECTOR:
838     accept = clip_sector (x1, y1, x2, y2, m_u, m_v);
839     break;
840   default:
841     sys_error (ERR_WARNING, "Illegal pelem type %d [pelem_clip_line]", m_type);
842     break;
843   }
844
845   return(accept);
846 }
847
848
849 // METHOD IDENTIFICATION 
850 //    PhantomElement::isPointInside             Check if point is inside pelem
851 //
852 // SYNOPSIS
853 //    is_point_inside (pelem, x, y, coord_type)
854 //    double x, y               Point to see if lies in pelem
855 //    int coord_type            Coordinate type (PELEM_COORD or PHM_COORD)
856 //
857 // RETURNS
858 //    true if point lies within pelem
859 //    false if point lies outside of pelem
860
861 bool
862 PhantomElement::isPointInside (double x, double y, const CoordType coord_type) const
863 {
864   if (coord_type == PHM_COORD) {
865     xform_mtx2 (m_xformPhmToObj, x, y);
866   } else if (coord_type != PELEM_COORD) {
867     sys_error(ERR_WARNING, "Illegal coordinate type in pelem_is_point_inside");
868     return (false);
869   }
870
871   switch (m_type) {
872   case PELEM_RECTANGLE:
873     if (x > 1. || x < -1. || y > 1. || y < -1.)
874       return (false);
875     else
876       return (true);
877     break;
878   case PELEM_TRIANGLE:
879     if (y < 0. || y > 1. - x || y > 1. + x)
880       return (false);
881     else
882       return (true);
883     break;
884   case PELEM_ELLIPSE:
885     if (x > 1. || x < -1. || y > 1. || y < -1.)
886       return (false);
887     if (x * x + y * y > 1.)             // check if inside unit circle
888       return (false);
889     else
890       return (true);
891     break;
892
893     // for clipping segments & sectors, must NOT scale by (1/u, 1/v)
894     // because this destroys information about size of arc component 
895
896   case PELEM_SEGMENT:
897     if (x > 1. || x < -1. || y > 0.)
898         return (false);         // clip against y > 0 
899     x *= m_u;                   // put back u & v scale 
900     y *= m_v;
901     if (x * x + (y-m_v) * (y-m_v) > m_u * m_u + m_v * m_v)
902       return (false);           // clip against circle, r = sqrt(@)
903     else
904       return (true);
905     break;
906   case PELEM_SECTOR:
907       if (x > 1. || x < -1. || y > 1.)   // extent 
908       return (false);
909       if (y > 1. - x || y > 1. + x)      // triangle    
910           return (false);                      // clip against triangle 
911       x *= m_u;                // circle: put back u & v scale 
912     y *= m_v;
913     if (x * x + (y-m_v) * (y-m_v) > m_u * m_u + m_v * m_v)
914         return (false);                // clip against circle 
915     else
916       return (true);
917   break;
918   default:
919     sys_error (ERR_WARNING, "Illegal pelem type in pelem_is_point_inside()");
920     break;
921   }
922
923   return (false);
924 }
925
926