HACKISH: Display chipseq peak window track
[mussa.git] / alg / glseqbrowser.cpp
1 #include "alg/glseqbrowser.hpp"
2 #include "mussa_exceptions.hpp"
3
4 #include <math.h>
5 #include <iostream>
6 #include <sstream>
7 #include <stdexcept>
8
9 using namespace std;
10
11 GlSeqBrowser::GlSeqBrowser()
12   : border_width(25),
13     cur_ortho(400.0, 0.0, 600.0, 0.0),
14     viewport_size(600, 400),
15     viewport_center((cur_ortho.right-cur_ortho.left)/2+cur_ortho.left),
16     zoom_level(2),
17     color_mapper(),
18     track_container()
19 {
20 }
21
22 GlSeqBrowser::GlSeqBrowser(const GlSeqBrowser& gt)
23   : border_width(gt.border_width),
24     cur_ortho(gt.cur_ortho),
25     viewport_size(gt.viewport_size),
26     viewport_center(gt.viewport_center),
27     zoom_level(gt.zoom_level),
28     color_mapper(gt.color_mapper),
29     track_container(gt.track_container),
30     path_segments(gt.path_segments)
31 {
32 }
33
34 void GlSeqBrowser::initializeGL()
35 {
36   glEnable(GL_DEPTH_TEST);
37   glClearColor(1.0, 1.0, 1.0, 0.0);
38   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
39   glShadeModel(GL_FLAT);
40 }
41
42 void GlSeqBrowser::resizeGL(int width, int height)
43 {
44   viewport_size.x = width;
45   viewport_size.y = height;
46   glViewport(0, 0, (GLsizei)width, (GLsizei)height);
47   update_viewport(viewport_center, zoom_level);
48 }
49
50 void GlSeqBrowser::paintGL() const
51 {
52   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
53
54   glPushMatrix();
55   glMatrixMode(GL_PROJECTION);
56   glLoadIdentity();
57   glOrtho(cur_ortho.left, cur_ortho.right,
58           cur_ortho.bottom, cur_ortho.top,
59           -50.0, 50);
60
61   draw();
62
63   glPopMatrix();
64   glFlush();
65 }
66
67 void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize, const rect<float>& r)
68 {
69   GLuint *ptr;
70   GLuint names;
71   GLuint consumed_names = 0;
72   float z1;
73   float z2;
74   GLuint objtype;
75   GLuint objid;
76   GLuint path_index = 0;
77   GLuint pair_key_0 = 0;
78   GLuint pair_key_1 = 0;
79   TrackRegion track;
80
81   selected_paths.clear();
82   selected_tracks.clear();
83
84   ptr = (GLuint *) buffer;
85   if (hits > 0)
86     selectedMode = true;
87   for (GLuint i=0; i < hits; ++i)
88   {
89     if ((i + 5) > bufsize) {
90       std::clog << "*** selection overflow***" << std::endl;
91     } else {
92       consumed_names = 0;
93       names = *ptr++;
94       z1 = ((float)*ptr++)/0x7fffffff;
95       z2 = ((float)*ptr++)/0x7fffffff;
96       objtype = *ptr++; ++consumed_names;
97       switch (objtype) {
98         case MussaSegment:
99           path_index = *ptr++; ++consumed_names;
100           pair_key_0 = *ptr++; ++consumed_names;
101           pair_key_1 = *ptr++; ++consumed_names;
102           if (path_index < path_segments.size()) {
103             segment_key k(pair_key_0, pair_key_1);
104             pair_segment_map::iterator psm_i;
105             psm_i = path_segments[path_index].find(k);
106             if (psm_i != path_segments[path_index].end()) {
107               Segment &seg = psm_i->second;
108               selected_paths.insert(seg.path_ids.begin(), seg.path_ids.end());
109             }
110             // else something else is wrong
111           } else {
112             // something wasn't right
113             clog << "invalid path_index " << path_index 
114                  << " should have been [0,"<<path_segments.size()
115                  << ") " << endl;
116           }
117           break;
118         case MussaTrack:
119         {
120           objid = *ptr++; ++consumed_names;
121
122           int left = track_container[objid]->leftbase(r.left);
123           int right = track_container[objid]->rightbase(r.right);
124           // the static_cast should be ok, since basepairs line up on 
125           // integral values
126           //TrackRegion track(objid, left, right); 
127           track.set(objid, left, right);
128           selected_tracks.push_back(track);
129           //clog << "selected track " << objid
130           //     << "(" << left << ", " << right << ")" << endl;
131         }
132         break;
133         default:
134           cout << "unknown type " << objtype << " ";
135           for(; consumed_names < names; ++consumed_names) {
136             cout << consumed_names << "," << *ptr++ << " ";
137           }
138           cout << endl;
139           break;
140       }
141     }
142   }
143 }
144
145 void GlSeqBrowser::selectRegion(int top, int left, int bottom, int right)
146 {
147   GLfloat x_left = viewportXtoWorldX(left);
148   GLfloat x_right = viewportXtoWorldX(right);cur_ortho.left;
149
150   if (top > bottom) {
151     // woah, someone gave us a rectangle with the origin in the lower left
152     int temp = top;
153     bottom = top;
154     top = temp;
155   }
156   // swap the orientation of canvas coordinates
157   GLfloat y_top = viewportYtoWorldY(bottom);
158   GLfloat y_bottom = viewportYtoWorldY(top);
159   selectedRegion = rect<float>(y_top, x_left, y_bottom, x_right);
160
161   // hopefully this will make a buffer big enough to receive 
162   // everything being selected
163   //const size_t pathz_count = mussaAnalysis->paths().refined_pathz.size();
164   //const GLuint select_buf_size = 1 + 5 * (pathz_count + sequences.size());
165   const GLuint select_buf_size = 500000;
166   GLuint selectBuf[select_buf_size];
167   glSelectBuffer(select_buf_size, selectBuf);
168   GLint hits;
169
170   (void)glRenderMode(GL_SELECT);
171   glPushMatrix();
172   glMatrixMode(GL_PROJECTION);
173   glLoadIdentity();
174   glOrtho(x_left, x_right, y_top, y_bottom, -50.0, 50.0);
175   glMatrixMode(GL_MODELVIEW);
176   glLoadIdentity();
177
178   draw();
179
180   glFlush();
181   glPopMatrix();
182   hits = glRenderMode(GL_RENDER);
183   processSelection(hits, selectBuf, select_buf_size, selectedRegion);
184 }
185
186 void GlSeqBrowser::clearSelection()
187 {
188   selected_paths.clear();
189   selected_tracks.clear();
190   selectedMode = false;  
191 }
192
193 float GlSeqBrowser::border() const
194 {
195   return border_width;
196 }
197
198 float GlSeqBrowser::left() const
199
200   float left;
201   if (track_container.size() == 0)
202   {
203     return cur_ortho.left;
204   } else {
205     vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();    
206     left = (*track_i)->x();
207     for( ; track_i != track_container.end(); ++track_i)
208     {
209       if ((*track_i)->x() < left) {
210         left = (*track_i)->x();
211       }
212     }
213     return left-border_width;
214   }
215 }
216
217 float GlSeqBrowser::right() const
218
219   float right;
220   if (track_container.size() == 0) {
221     return cur_ortho.right;
222   } else {
223     vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();
224     right = (*track_i)->right();
225     for( ; track_i != track_container.end(); ++track_i) {
226       if ((*track_i)->right() > right)
227         right = (*track_i)->right();
228     }
229     return right+border_width;
230   }
231 }
232
233 float GlSeqBrowser::get_pixel_width() const
234 {
235   GLint viewport[4];
236   glGetIntegerv(GL_VIEWPORT, viewport);
237   GLint vp_width = viewport[3]; // grab the viewport width
238   
239   return round((cur_ortho.right-cur_ortho.left)/vp_width);
240 }  
241
242 void GlSeqBrowser::setViewportCenter(float x)
243 {
244   update_viewport(x, zoom_level);
245   viewport_center = x;
246 }
247
248 float GlSeqBrowser::viewportLeft() const
249
250   return cur_ortho.left; 
251 }
252
253 float GlSeqBrowser::viewportCenter() const
254 {
255   return viewport_center;
256 }
257
258 float GlSeqBrowser::viewportRight() const
259
260   return cur_ortho.right; 
261 }
262
263 float GlSeqBrowser::viewportHeight() const
264 {
265   return cur_ortho.top - cur_ortho.bottom;
266 }
267
268 float GlSeqBrowser::viewportWidth() const
269 {
270   return cur_ortho.right - cur_ortho.left;
271 }
272
273 int GlSeqBrowser::viewportPixelHeight() const
274 {
275   return viewport_size.y;
276 }
277
278 int GlSeqBrowser::viewportPixelWidth() const
279 {
280   return viewport_size.x;
281 }
282
283 double GlSeqBrowser::zoomOut()
284 {
285
286   if (right() - left() > 0) {
287     cur_ortho.left = left();
288     cur_ortho.right = right();
289     zoom_level =  (right() - left()) / (double)viewport_size.x;
290     return zoom_level;
291   } else {
292     // made up number representing 50 bp / pixel
293     return 50.0;
294   }
295 }
296
297 double GlSeqBrowser::zoomToSequence()
298 {
299   // (experimentally determined zoom level)
300   const double friendly_zoom = 0.10;
301   setZoom(friendly_zoom);
302   return friendly_zoom;
303 }
304
305 void GlSeqBrowser::setZoom(double new_zoom)
306 {
307   update_viewport(viewport_center, new_zoom);
308   zoom_level = new_zoom;
309 }
310
311 double GlSeqBrowser::zoom() const
312 {
313   return zoom_level;
314 }
315
316 void GlSeqBrowser::setColorMapper(AnnotationColorsRef cm)
317 {
318   color_mapper = cm;
319 }
320
321 const AnnotationColorsRef GlSeqBrowser::colorMapper()
322 {
323   return color_mapper;
324 }
325
326 void GlSeqBrowser::clear()
327 {
328   clear_selection();
329   clear_links();
330   path_segments.clear();
331   track_container.clear();
332 }
333
334 void GlSeqBrowser::clear_selection()
335 {
336   selectedMode = false;
337   selectedRegion.clear();
338   selected_paths.clear();
339   selected_tracks.clear();
340 }
341
342 void GlSeqBrowser::push_sequence(const Sequence& s)
343 {
344   GlSequenceRef gs(new GlSequence(s, color_mapper));
345   push_sequence(gs);
346 }
347
348 void GlSeqBrowser::push_sequence(SequenceRef s)
349 {
350   GlSequenceRef gs(new GlSequence(*s, color_mapper));
351   push_sequence(gs);
352 }
353
354 void GlSeqBrowser::push_sequence(GlSequence gs)
355 {
356   GlSequenceRef new_gs(new GlSequence(gs));
357   push_sequence(new_gs);
358 }
359
360 void GlSeqBrowser::push_sequence(GlSequenceRef gs)
361 {
362   ColorRef default_color(GlSequence::default_gene_color());
363   ColorRef default_chipseq_color(GlSequence::default_chipseq_color());
364   GlSequenceRef new_gs(new GlSequence(gs));
365   new_gs->update_annotation_draw_function("gene", draw_narrow_track, default_color);
366   new_gs->update_annotation_draw_function("chipseq_peak_window",
367                                           draw_chipseq_window,
368                                           default_chipseq_color);
369   // mark where the sequence is
370   new_gs->add_annotations_for_defined_sequence(draw_summarized_track);
371   
372   clear_links();
373   track_container.push_back(new_gs);
374   update_layout();
375   if (track_container.size() > 1)
376     path_segments.push_back(pair_segment_map());
377 }
378
379 const std::vector<GlSequenceRef >& GlSeqBrowser::sequences() const
380 {
381   return track_container;
382 }
383
384 void GlSeqBrowser::clear_links()
385 {
386   path_segments.clear();
387   for (int i = track_container.size()-1; i > 0; --i)
388   {
389     path_segments.push_back(pair_segment_map());
390   }
391   pathid = 0;
392 }
393
394 void 
395 GlSeqBrowser::link(const vector<int>& path, const vector<bool>& rc, int length)
396 {
397   if (path.size() < 2) {
398     // should i throw an error instead?
399     return;
400   }
401   if (path.size() != track_container.size() ) {
402     stringstream msg;
403     msg << "Path size [" << path.size() << "] and track size [" 
404         << track_container.size() << "] don't match" << endl;
405     throw mussa_error(msg.str());
406   }
407   if (path.size() != rc.size()) {
408     throw runtime_error("path and reverse compliment must be the same length");
409   }
410   vector<int>::const_iterator path_i = path.begin();
411   vector<bool>::const_iterator rc_i = rc.begin();
412   int track_i = 0;
413   int prev_x = *path_i; ++path_i;
414   bool prev_rc = *rc_i; ++rc_i;
415   while (path_i != path.end() and rc_i != rc.end())
416   {
417     segment_key p(prev_x, *path_i);
418     pair_segment_map::iterator found_segment = path_segments[track_i].find(p);
419     if (found_segment == path_segments[track_i].end()) {
420       // not already found
421       float y1 = track_container[track_i]->y();
422             y1 -= track_container[track_i]->height()/2;
423       float y2 = track_container[track_i+1]->y();
424             y2 += track_container[track_i+1]->height()/2;
425       
426       bool rcFlag = (prev_rc or *rc_i) and !(prev_rc and *rc_i);
427       Segment s(prev_x, y1, *path_i, y2, rcFlag, length);
428       s.path_ids.insert(pathid);
429       path_segments[track_i][p] = s;
430     } else {
431       //found
432       found_segment->second.path_ids.insert(pathid);
433       // make each segment the size of the largest of any link between these 
434       // two bases
435       if (found_segment->second.length < length) {
436         found_segment->second.length = length;
437       }
438     }
439     prev_x = *path_i;
440     prev_rc = *rc_i;
441     ++track_i;
442     ++path_i;
443     ++rc_i;
444   }
445   // pathid is reset by push_sequence
446   ++pathid;
447 }
448
449 void GlSeqBrowser::setSelectedPaths(std::vector<int> paths)
450 {
451   selected_paths.clear();
452   for(std::vector<int>::iterator itor = paths.begin();
453       itor != paths.end();
454       ++itor)
455   {
456     selected_paths.insert(*itor);
457   }
458 }
459
460 const set<int>& GlSeqBrowser::selectedPaths() const
461 {
462   return selected_paths;
463 }
464
465 void GlSeqBrowser::appendSelectedTrack(GLuint track, int start, int stop)
466 {
467   selected_tracks.push_back(TrackRegion(track, start, stop));
468 }
469
470 list<TrackRegion> GlSeqBrowser::selectedTracks() const 
471 {
472   return selected_tracks;
473 }
474
475 //! copy sequence from selected track using formating function
476 template<class Item>
477 size_t GlSeqBrowser::copySelectedTracks(std::list<Item>& result, 
478              Item (*formatter)(const Sequence& s, int left, int right))
479 {
480   size_t base_pairs_copied = 0;
481   result.clear();
482
483   for(selected_track_iterator track_i = selected_tracks.begin();
484       track_i != selected_tracks.end();
485       ++track_i)
486   {
487     int track_index = track_i->track_id;
488     if (track_index >= track_container.size()) {
489       // should this be an exception instead?
490       clog << "track " << track_index << " > " << track_container.size() 
491            << endl;
492     } else {
493       // we should be safe
494       Sequence seq(*track_container[track_index]);
495       result.push_back(formatter(seq, track_i->left, track_i->right));
496       base_pairs_copied += max(track_i->right-track_i->left, 0);
497     }
498   }
499   return base_pairs_copied;
500 }
501
502 //! copy sequence from selected tracks as FASTA sequences
503 size_t GlSeqBrowser::copySelectedTracksAsFasta(std::string& copy_buffer)
504 {
505   std::list<std::string> result;
506   struct AsFasta {
507     static string formatter(const Sequence& seq, int left, int right)
508     {
509       stringstream s;
510       s << ">" << seq.get_fasta_header() 
511         << "|" << "subregion=" << left << "-" << right+1
512         << std::endl
513         << seq.subseq(left, right-left+1) << std::endl;
514       return s.str();
515     }
516   };
517   size_t base_pairs_copied = copySelectedTracks(result, AsFasta::formatter);
518   // I wish there was some way to use for_each and bind here
519   for (list<string>::iterator result_i = result.begin();
520        result_i != result.end();
521        ++result_i)
522   {
523     copy_buffer.append(*result_i);
524   }
525   return base_pairs_copied;
526 }
527
528 //! copy sequence from selected tracks as new sequences
529 size_t GlSeqBrowser::copySelectedTracksAsSequences(std::list<Sequence>& result)
530 {
531   struct AsSequence {
532     static Sequence formatter(const Sequence& seq, 
533                               int left, 
534                               int right)
535     {
536       return seq.subseq(left, right-left+1);
537     }
538   };
539   return copySelectedTracks(result, AsSequence::formatter);
540 }
541
542 size_t GlSeqBrowser::copySelectedTracksAsSeqLocation(
543     std::list<SequenceLocation>& result)
544 {
545   struct AsSeqLocation {
546     static SequenceLocation formatter(const Sequence& seq, 
547                                       int left, 
548                                       int right)
549     {
550       return SequenceLocation(seq, left, right);
551     }
552   };
553   return copySelectedTracks(result, AsSeqLocation::formatter);
554 }
555
556 //! copy sequence from selected tracks as plain sequences
557 size_t GlSeqBrowser::copySelectedTracksAsString(std::string& copy_buffer)
558 {
559   std::list<string> result;
560   struct AsString {
561     static string formatter(const Sequence& seq, 
562                             int left, 
563                             int right)
564     {
565       stringstream s;
566       s << seq.subseq(left, right-left+1);
567       return s.str();
568     }
569   };
570
571   size_t base_pairs_copied = copySelectedTracks(result, AsString::formatter);
572   // I wish there was some way to use for_each and bind here
573   for (list<string>::iterator result_i = result.begin();
574        result_i != result.end();
575        ++result_i)
576   {
577     copy_buffer.append(*result_i);
578   }
579   return base_pairs_copied;
580 }
581
582 void GlSeqBrowser::centerOnPath(const vector<int>& paths)
583 {
584   if (paths.size() != track_container.size()) {
585     throw mussa_error("Path length didn't match the number of sequences");
586   }
587
588   for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
589   {
590     // -15 = shift more to the left
591     track_container[track_i]->setX((viewport_center-15) - paths[track_i]);
592   }
593 }
594
595 void GlSeqBrowser::update_viewport(float center, double new_zoom)
596 {
597   // limit how close we can get
598   if (new_zoom < 0.01) {
599     new_zoom = 0.01;
600   }
601   double new_width = (new_zoom * (float)viewport_size.x);
602   cur_ortho.left = center-new_width/2.0;
603   cur_ortho.right = center+new_width/2.0;
604 }
605
606 void GlSeqBrowser::update_layout()
607 {
608   typedef std::vector<boost::shared_ptr<GlSequence> >::iterator glseq_itor_type;
609   float available_height = (float)cur_ortho.top - 2 * (float)border_width;
610   float max_base_pairs = 0;
611   size_t track_count = track_container.size();
612
613   if (track_count > 1) {
614     // we have several sequences
615     float track_spacing = available_height / (track_count-1);
616     float y = available_height + (float)border_width;
617     for(glseq_itor_type seq_i = track_container.begin();
618         seq_i != track_container.end();
619         ++seq_i, y-=track_spacing)
620     {
621       (*seq_i)->setX(0);
622       (*seq_i)->setY(y);
623       if ((*seq_i)->size() > max_base_pairs)
624         max_base_pairs = (*seq_i)->size();
625     }
626   } else if (track_count == 1) {
627     // center the single track
628     glseq_itor_type seq_i = track_container.begin();
629     (*seq_i)->setX(0);
630     (*seq_i)->setY(viewport_size.x /2);
631     max_base_pairs = (*seq_i)->size();
632   } else {
633     // nothing to do as we're empty
634     return;
635   }
636   cur_ortho.right = max_base_pairs + border_width;
637   cur_ortho.left = -border_width;
638   cur_ortho.top = viewport_size.x;
639   cur_ortho.bottom = 0;
640   viewport_center = (cur_ortho.width()/2) + cur_ortho.left;
641   zoomOut();
642 }
643
644 void GlSeqBrowser::draw() const
645 {
646   glMatrixMode(GL_MODELVIEW);
647   glInitNames();
648   glPushName(MussaSegment);
649   draw_segments();
650   glLoadName(MussaTrack);
651   draw_tracks();
652   glPopName();
653   // a selection shouldn't have a glName associated with it
654   draw_selection();
655 }
656
657 void GlSeqBrowser::draw_selection() const
658 {
659   // draw selection box
660   glEnable(GL_BLEND);
661   glDepthMask(GL_FALSE);
662   if (selectedMode) {
663     glColor4f(0.6, 0.6, 0.6, 0.9);
664     glRectf(selectedRegion.left, selectedRegion.top, 
665             selectedRegion.right, selectedRegion.bottom);
666   }
667   glDepthMask(GL_TRUE);
668   glDisable(GL_BLEND);
669 }
670
671 void GlSeqBrowser::draw_tracks() const
672 {
673   for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
674   {
675     glPushName(track_i);
676     track_container[track_i]->draw(cur_ortho.left, cur_ortho.right);
677     glPopName();
678   }
679 }
680
681 void GlSeqBrowser::draw_segments() const
682 {
683   glLineWidth(1);
684   glEnable(GL_BLEND);
685   glDepthMask(GL_FALSE);
686   const float zdepth = -1.0;
687   const float min_segment_width = max((float)(1.0), get_pixel_width());
688   
689   // each vector contains path_segment_maps of all the connections
690   // between this track and the next
691   path_segment_map_vector::const_iterator psmv_i;
692   for(psmv_i = path_segments.begin();
693       psmv_i != path_segments.end();
694       ++psmv_i)
695   {
696     path_segment_map_vector::difference_type path_index;
697     path_index = psmv_i - path_segments.begin();
698     // these maps contain the pair index (used so we dont keep drawing the
699     // same segment) and the actual segment structure.
700     pair_segment_map::const_iterator psm_i;
701     for(psm_i = psmv_i->begin();
702         psm_i != psmv_i->end();
703         ++psm_i)
704     {
705       // grab the index into our segment map
706       const segment_key& key = psm_i->first;
707       // the second element of our map pair is a segment
708       const Segment &s = psm_i->second;
709       // need to do something so we can detect our selection
710       vector<int> selected;
711       set_intersection(selected_paths.begin(), selected_paths.end(),
712                        s.path_ids.begin(), s.path_ids.end(),
713                        back_inserter(selected));
714
715       if (not s.reversed) {
716         // forward
717         if (selected_paths.size() == 0 or selected.size() > 0) {
718           glColor4f(1.0, 0.0, 0.0, 1.0);
719         } else {
720           glColor4f(1.0, 0.7, 0.7, 0.4);
721         }
722       } else { 
723         // reverse
724         if (selected_paths.size() == 0 or selected.size() > 0) {
725           glColor4f(0.0, 0.0, 1.0, 1.0);
726         } else {
727           glColor4f(0.7, 0.7, 1.0, 0.4);
728         }
729       }
730       // save the multipart name for our segment
731       glPushName(path_index); glPushName(key.first); glPushName(key.second);
732       float seq_start_x = s.start.x 
733                         + track_container[path_index]->x();
734       float seq_end_x = s.end.x
735                       + track_container[path_index+1]->x();
736       if (s.length <= min_segment_width) {
737         // use lines for elements of length <=1 or < 1 pixel.
738         // and try to center the line
739         const float offset = s.length * 0.5;
740         glBegin(GL_LINES);
741           glVertex3f(seq_start_x+offset, s.start.y, -1);
742           glVertex3f(seq_end_x  +offset, s.end.y, -1);
743         glEnd();
744       } else {
745         // otherwise use quads
746         // compute length
747         float seq_start_x_length = s.start.x 
748                                  + s.length
749                                  + track_container[path_index]->x();
750         float seq_end_x_length = s.end.x
751                                + s.length
752                                + track_container[path_index+1]->x();
753         glBegin(GL_QUADS);
754           glVertex3f(seq_start_x, s.start.y, zdepth);
755           glVertex3f(seq_end_x, s.end.y, zdepth);
756           glVertex3f(seq_end_x_length, s.end.y, zdepth);
757           glVertex3f(seq_start_x_length, s.start.y, zdepth);
758         glEnd();
759       }      
760       // clear the names
761       glPopName(); glPopName(); glPopName();
762     }
763   }
764   glDepthMask(GL_TRUE);
765   glDisable(GL_BLEND);
766 }