1 
2 // render.h [pengine]
3 
4 // Copyright 2004-2006 Jasmine Langridge, jas@jareiko.net
5 // License: GPL version 2 (see included gpl.txt)
6 
7 
8 #include <cmath>
9 #include "vbuffer.h"
10 
11 
12 
13 struct PParticle_s {
14   vec3f pos,linvel;
15   float life;
16 
17   vec2f orix,oriy; // orientation vectors (2d)
18 };
19 
20 class PParticleSystem {
21 protected:
22   float colorstart[4],colorend[4];
23   float startsize, endsize;
24   float decay;
25   const PTexture *tex;
26   GLenum blendparam1, blendparam2;
27 
28   std::vector<PParticle_s> part;
29 
30 public:
PParticleSystem()31   PParticleSystem() {
32     colorstart[0] = colorstart[1] = colorstart[2] = colorstart[3] = 1.0;
33     colorend[0] = colorend[1] = colorend[2] = 1.0; colorend[3] = 0.0;
34     startsize = 0.0;
35     endsize = 1.0;
36     decay = 1.0;
37     tex = nullptr;
38     blendparam1 = GL_SRC_ALPHA;
39     blendparam2 = GL_ONE;
40   }
41 
42 public:
setColorStart(float r,float g,float b,float a)43   void setColorStart(float r, float g, float b, float a) {
44     colorstart[0] = r; colorstart[1] = g; colorstart[2] = b; colorstart[3] = a;
45   }
setColorEnd(float r,float g,float b,float a)46   void setColorEnd(float r, float g, float b, float a) {
47     colorend[0] = r; colorend[1] = g; colorend[2] = b; colorend[3] = a;
48   }
setColor(float r,float g,float b)49   void setColor(float r, float g, float b) {
50     colorstart[0] = colorend[0] = r;
51     colorstart[1] = colorend[1] = g;
52     colorstart[2] = colorend[2] = b;
53   }
setSize(float start,float end)54   void setSize(float start, float end) {
55     startsize = start; endsize = end;
56   }
setDecay(float _decay)57   void setDecay(float _decay) {
58     decay = _decay;
59   }
setTexture(const PTexture * texptr)60   void setTexture(const PTexture *texptr) {
61     tex = texptr;
62   }
setBlend(GLenum b1,GLenum b2)63   void setBlend(GLenum b1, GLenum b2) {
64     blendparam1 = b1;
65     blendparam2 = b2;
66   }
67 
68   void addParticle(const vec3f &pos, const vec3f &linvel);
69 
70   void tick(float delta);
71 
72   friend class PSSRender;
73 };
74 
75 struct PVert_tv {
76   vec2f st;
77   vec3f xyz;
78 };
79 
80 #define PTEXT_HZA_LEFT    0x00000000 // default
81 #define PTEXT_HZA_CENTER  0x00000001
82 #define PTEXT_HZA_RIGHT   0x00000002
83 #define PTEXT_VTA_BOTTOM  0x00000000 // default
84 #define PTEXT_VTA_CENTER  0x00000100
85 #define PTEXT_VTA_TOP     0x00000200
86 
87 class PSSRender : public PSubsystem {
88 private:
89   vec3f cam_pos;
90   mat44f cam_orimat;
91 
92 public:
93   PSSRender(PApp &parentApp);
94   ~PSSRender();
95 
96   void tick(float delta, const vec3f &eyepos, const mat44f &eyeori, const vec3f &eyevel);
97 
98   void render(PParticleSystem *psys);
99 
100   void drawModel(PModel &model, PSSEffect &ssEffect, PSSTexture &ssTexture);
101 
102   void drawText(const std::string &text, uint32 flags);
103   vec2f getTextDims(const std::string &text);
104 };
105 
106 
107 
108 class PSSTexture : public PSubsystem {
109 private:
110   PResourceList<PTexture> texlist;
111 
112 public:
113   PSSTexture(PApp &parentApp);
114   ~PSSTexture();
115 
116   PTexture *loadTexture(const std::string &name, bool genMipmaps = true, bool clamp = false);
117 };
118 
119 
120 class PImage {
121 private:
122   uint8 *data;
123   int cx,cy,cc;
124 
125 public:
PImage()126   PImage () : data (nullptr) { }
PImage(const std::string & filename)127   PImage (const std::string &filename) : data (nullptr) { load (filename); }
PImage(int _cx,int _cy,int _cc)128   PImage (int _cx, int _cy, int _cc) : data (nullptr) { load (_cx, _cy, _cc); }
129   ~PImage ();
130 
131   void load (const std::string &filename);
132   void load (int _cx, int _cy, int _cc);
133   void unload ();
134 
135   void expandChannels();
136 
getcx()137   int getcx() const { return cx; }
getcy()138   int getcy() const { return cy; }
getcc()139   int getcc() const { return cc; }
getData()140   uint8 *getData() { return data; }
141 
getData()142   const uint8 * getData() const
143   {
144     return data;
145   }
146 
getByte(int i)147   uint8 & getByte(int i)
148   {
149       return data[i];
150   }
151 
getByte(int i)152   uint8 getByte(int i) const
153   {
154       return data[i];
155   }
156 
swap(PImage & other)157   void swap (PImage &other) throw ()
158   {
159     { uint8 *tmp = data; data = other.data; other.data = tmp; }
160     { int tmp = cx; cx = other.cx; other.cx = tmp; }
161     { int tmp = cy; cy = other.cy; other.cy = tmp; }
162     { int tmp = cc; cc = other.cc; other.cc = tmp; }
163   }
164 };
165 
166 
167 class PTexture : public PResource {
168 private:
169   GLuint texid;
170   GLenum textarget;
171 
172 public:
PTexture()173   PTexture () : texid (0) { }
PTexture(const std::string & filename,bool genMipmaps,bool clamp)174   PTexture (const std::string &filename, bool genMipmaps, bool clamp) : texid (0) { load (filename, genMipmaps, clamp); }
PTexture(PImage & img,bool genMipmaps,bool clamp)175   PTexture (PImage &img, bool genMipmaps, bool clamp) : texid (0) { load (img, genMipmaps, clamp); }
~PTexture()176   ~PTexture() { unload (); }
177 
178   void load (const std::string &filename, bool genMipmaps, bool clamp);
179   void load(PImage &img, bool genMipmaps = true, bool clamp = false);
180   void loadPiece(PImage &img, int offx, int offy, int sizex, int sizey, bool genMipmaps = true, bool clamp = false);
181   void loadAlpha(const std::string &filename, bool genMipmaps = true, bool clamp = false);
182   void loadAlpha(PImage &img, bool genMipmaps = true, bool clamp = false);
183   void loadCubeMap(const std::string &filenamePrefix, const std::string &filenameSuffix, bool genMipmaps = true);
184   void unload();
185 
186   void bind() const;
187 
188   static void unbind();
189 };
190 
191 
192 
193 
194 class PSSEffect : public PSubsystem {
195 private:
196   PResourceList<PEffect> fxlist;
197 
198 public:
199   PSSEffect(PApp &parentApp);
200   ~PSSEffect();
201 
202   PEffect *loadEffect(const std::string &name);
203 };
204 
205 
206 
207 #define CULLFACE_NONE       0
208 #define CULLFACE_CW         1
209 #define CULLFACE_CCW        2
210 
211 #define BLEND_NONE          0
212 #define BLEND_ADD           1
213 #define BLEND_MULTIPLY      2
214 #define BLEND_ALPHA         3
215 #define BLEND_PREMULTALPHA  4
216 
217 
218 struct fx_renderstate_s {
219   bool depthtest;
220 
221   bool lighting;
222   bool lightmodeltwoside;
223 
224   struct {
225     GLenum func;
226     float ref;
227   } alphatest;
228 
229   int cullface;
230 
231   int blendmode;
232 
233   struct {
234     int texindex;
235   } texunit[1];
236 };
237 
238 
239 struct fx_pass_s {
240   fx_renderstate_s rs;
241 };
242 
243 struct fx_technique_s {
244   std::string name;
245 
246   std::vector<fx_pass_s> pass;
247 
248   bool validated;
249   bool textures_ready;
250 
fx_technique_sfx_technique_s251   fx_technique_s() {
252     validated = false;
253     textures_ready = false;
254   }
255 };
256 
257 struct fx_texture_s {
258   std::string name;
259 
260   std::string filename;
261 
262   GLenum type;
263 
264   // This is filled in just before rendering
265   PTexture *texobject;
266 
fx_texture_sfx_texture_s267   fx_texture_s() {
268     texobject = nullptr;
269   }
270 };
271 
272 
273 class PEffect : public PResource {
274 private:
275   // resources
276   std::vector<fx_texture_s> tex;
277 
278   // techniques
279   std::vector<fx_technique_s> tech;
280 
281   int cur_tech;
282 
283 public:
284   PEffect(const std::string &filename);
285   ~PEffect();
286 
287   void unload();
288 
289   void loadFX(const std::string &filename);
290   void loadMTL(const std::string &filename);
291 
292   int getNumTechniques();
293   bool validateTechnique(int technique);
294   const std::string &getTechniqueName(int technique);
295   bool findTechnique(const std::string &techname, int *technique);
296 
297   bool setCurrentTechnique(int technique);
298   int getCurrentTechnique();
299 
300   bool setFirstValidTechnique();
301 
302   bool renderBegin(int *numPasses, PSSTexture &sstex);
303   void renderPass(int pass);
304   void renderEnd();
305 
306 private:
307   void migrateRenderState(fx_renderstate_s *rs_old, fx_renderstate_s *rs_new);
308 };
309 
310 
311 
312 
313 class PSSModel : public PSubsystem {
314 private:
315   PResourceList<PModel> modlist;
316 
317 public:
318   PSSModel(PApp &parentApp);
319   ~PSSModel();
320 
321   PModel *loadModel(const std::string &name);
322 };
323 
324 
325 class PFace {
326 public:
327   vec3f facenormal;
328   uint32 vt[3];
329   uint32 tc[3];
330   uint32 nr[3];
331 
332 public:
333 //    PFace(vec
334 };
335 
336 
337 class PMesh {
338 public:
339   std::vector<vec3f> vert;
340   std::vector<vec2f> texco;
341   std::vector<vec3f> norm;
342   std::vector<PFace> face;
343 
344   std::string fxname;
345   PEffect *effect;
346 };
347 
348 
349 class PModel : public PResource {
350 public:
351   std::vector<PMesh> mesh;
352 
353   std::pair<vec3f, vec3f> getExtents() const;
354 
355 public:
356   PModel (const std::string &filename, float globalScale = 1.0);
357 
358 private:
359   void loadASE (const std::string &filename, float globalScale);
360   void loadOBJ (const std::string &filename, float globalScale);
361 };
362 
363 struct PTerrainFoliageBand {
364   float middle, range;
365   float density;
366   int trycount;
367   float scale;
368 
369   /*
370   float scalemin;
371   float scalemax;
372   */
373 
374   PTexture *sprite_tex;
375   int sprite_count;
376 };
377 
378 struct PTerrainFoliage {
379   vec3f pos;
380   float ang;
381   float scale;
382 };
383 
384 struct PTerrainFoliageSet {
385   std::vector<PTerrainFoliage> inst;
386 
387   PVBuffer buff[2];
388   int numvert, numelem;
389 };
390 
391 struct PRoadSignSet {
392     std::vector<PTerrainFoliage> inst;
393     PVBuffer buff[2];
394     int numvert;
395     int numelem;
396 };
397 
398 struct PTerrainTile {
399   int posx, posy;
400   int lru_counter;
401 
402   PVBuffer vert;
403   int numverts;
404 
405   PTexture tex;
406 
407   vec3f mins,maxs; // AABB
408 
409   //
410 
411   std::vector<PTerrainFoliageSet> foliage;
412   std::vector<PRoadSignSet> roadsignset;
413 };
414 
415 ///
416 /// @brief Loads road information.
417 ///
418 class RoadMap
419 {
420 public:
421 
422     RoadMap() = default;
423 
load(const PImage & img)424     bool load(const PImage &img)
425     {
426         if (img.getData() == nullptr)
427             return false;
428 
429         bx = img.getcx();
430         by = img.getcy();
431         bitmap.clear();
432         bitmap.reserve(img.getcx() * img.getcy());
433 
434         const std::size_t tb = img.getcx() * img.getcy() * img.getcc(); // Total Bytes
435 
436         for (auto pb = img.getData(); pb != img.getData() + tb; pb += img.getcc())
437         {
438             bool is_road = true;
439 
440             for (int i=0; i < img.getcc(); ++i)
441                 if (pb[i] != 0xFF) // the road is white
442                 {
443                     is_road = false;
444                     break;
445                 }
446 
447             bitmap.push_back(is_road);
448         }
449 
450         return true;
451     }
452 
is_loaded()453     bool is_loaded() const
454     {
455         return !bitmap.empty();
456     }
457 
458     ///
459     /// @brief Decides if provided point is on the road.
460     /// @param px           X coordinate of the point.
461     /// @param py           Y coordinate of the point.
462     /// @param ms           Map size.
463     /// @see `getMapSize()`.
464     /// @returns Whether or not the point is on the road.
465     /// @retval true        If no roadmap was loaded.
466     ///
isOnRoad(float px,float py,float ms)467     bool isOnRoad(float px, float py, float ms) const
468     {
469         if (bitmap.empty())
470             return true;
471 
472         if (px >= ms)
473         {
474             do
475                 px -= ms;
476             while (px > ms);
477         }
478         else
479         if (px < 0)
480         {
481             do
482                 px += ms;
483             while (px < 0);
484         }
485 
486         if (py >= ms)
487         {
488             do
489                 py -= ms;
490             while (py > ms);
491         }
492         else
493         if (py < 0)
494         {
495             do
496                 py += ms;
497             while (py < 0);
498         }
499 
500         long int x = std::lround(px * bx / ms);
501         long int y = std::lround(py * by / ms);
502 
503         CLAMP_UPPER(x, bx - 1);
504         CLAMP_UPPER(y, by - 1);
505         return bitmap.at(y * bx + x);
506     }
507 
508 private:
509 
510     std::vector<bool> bitmap;
511     int bx;
512     int by;
513 };
514 
515 struct road_sign
516 {
517 public:
518 
519 //
520 // being smart here makes the rest of the code more complicated;
521 // so let's be stupid for now...
522 //
523 #if 0
524     struct road_sign_location
525     {
526     public:
527 
528         float x;
529         float y;
530         float deg;
531 
532         road_sign_location(float x=0, float y=0, float deg=0):
533             x(x), y(y), deg(deg)
534         {
535         }
536     };
537 
538     std::vector<road_sign_location> location;
539 #endif
540 
541     PTexture   *sprite  = nullptr;
542 //  PTexture   *front   = nullptr;
543 //  PTexture   *back    = nullptr;
544     float       scale   = 1.0f;
545     float       x       = 0.0f;
546     float       y       = 0.0f;
547     float       deg     = 0.0f;
548 };
549 
550 class PTerrain // TODO: make this RAII conformant
551 {
552 protected:
553   bool loaded;
554 
555   int tilesize, tilecount, totsize, totmask, totsizesq;
556 
557   float scale_hz, scale_vt, scale_hz_inv, scale_vt_inv, scale_tile_inv;
558 
559   int cmaptotsize, cmaptilesize, cmaptotmask;
560 
561   //std::vector<uint8> hmap;
562   std::vector<float> hmap;
563 
564   PImage cmap;
565   PImage tmap; ///< Terrain map.
566   RoadMap rmap; ///< Road map.
567 
568   std::vector<float> fmap;
569   std::vector<PTerrainFoliageBand> foliageband;
570   std::vector<road_sign> roadsigns;
571 
572   std::list<PTerrainTile> tile;
573 
574   // tiles share index buffers
575   PVBuffer ind;
576   int numinds;
577 
578   PTexture *tex_hud_map;
579 
580 protected:
581 
582   PTerrainTile *getTile(int x, int y);
583 
getInterp(float x,float y,float * data)584   float getInterp(float x, float y, float *data) {
585     x *= scale_hz_inv;
586     int xi = (int)x;
587     if (x < 0.0) xi--;
588     x -= (float)xi;
589     int xiw = xi & totmask, xiw2 = (xiw+1) & totmask;
590 
591     y *= scale_hz_inv;
592     int yi = (int)y;
593     if (y < 0.0) yi--;
594     y -= (float)yi;
595     int yiw = yi & totmask, yiw2 = (yiw+1) & totmask;
596 
597     const int cx = totsize;
598 
599     float xv1,xv2;
600     if (y > 0.0) {
601       if (y < 1.0) {
602         if (x < y) {
603           xv1 = data[yiw*cx+xiw];
604           xv2 = INTERP(data[yiw2*cx+xiw],data[yiw2*cx+xiw2],x/y);
605         } else {
606           xv1 = INTERP(data[yiw*cx+xiw],data[yiw*cx+xiw2],(x-y)/(1.0-y));
607           xv2 = data[yiw2*cx+xiw2];
608         }
609         return INTERP(xv1,xv2,y);
610       } else {
611         return INTERP(data[yiw2*cx+xiw],data[yiw2*cx+xiw2],x);
612       }
613     } else {
614       return INTERP(data[yiw*cx+xiw],data[yiw*cx+xiw2],x);
615     }
616   }
617 
618 public:
619   PTerrain(XMLElement *element, const std::string &filepath, PSSTexture &ssTexture);
620   ~PTerrain();
621 
622   void unload();
623 
624   void render(const vec3f &campos, const mat44f &camorim);
625 
626   void drawSplat(float x, float y, float scale, float angle);
627 
628 
629   struct ContactInfo {
630     vec3f pos;
631     vec3f normal;
632   };
633 
634     ///
635     /// @brief Returns whether or not the given position is on road.
636     /// @param [in] pos         Position to be checked.
637     /// @returns Whether or not `pos` is on the road.
638     /// @retval true            If no roadmap was loaded.
639     /// @see `RoadMap`.
640     ///
getRmapOnRoad(const vec3f & pos)641     bool getRmapOnRoad(const vec3f &pos) const
642     {
643         return rmap.isOnRoad(pos.x, pos.y, getMapSize());
644     }
645 
646     ///
647     /// @brief Returns the color of the pixel in the colormap that corresponds
648     ///  to the given position in the terrain.
649     /// @note The height component Z is ignored.
650     /// @todo Should check if cmap.getcc() returns at least 3?
651     /// @todo Should check if cmap.getcx() == cmap.getcy()?
652     /// @todo Should remove paranoid clampings?
653     /// @todo Should actually measure performance of float vs int.
654     /// @param [in] pos   Position in the terrain.
655     /// @returns Color in OpenGL-style RGB.
656     ///
getCmapColor(const vec3f & pos)657     vec3f getCmapColor(const vec3f &pos) const
658     {
659         vec3f r;
660 #if 0
661         const float ms = getMapSize();
662         float px = pos.x;
663         float py = pos.y;
664 #else
665         const int ms = static_cast<int> (getMapSize());
666         int px = static_cast<int> (pos.x);
667         int py = static_cast<int> (pos.y);
668 #endif
669         if (px >= ms)
670         {
671             do
672                 px -= ms;
673             while (px > ms);
674         }
675         else
676         if (px < 0)
677         {
678             do
679                 px += ms;
680             while (px < 0);
681         }
682 
683         if (py >= ms)
684         {
685             do
686                 py -= ms;
687             while (py > ms);
688         }
689         else
690         if (py < 0)
691         {
692             do
693                 py += ms;
694             while (py < 0);
695         }
696 
697         long int x = std::lround(px * cmap.getcx() / getMapSize());
698         long int y = std::lround(py * cmap.getcy() / getMapSize());
699 
700         CLAMP_UPPER(x, cmap.getcx() - 1);
701         CLAMP_UPPER(y, cmap.getcy() - 1);
702         r.x = cmap.getByte((y * cmap.getcx() + x) * cmap.getcc() + 0) / 255.0f;
703         r.y = cmap.getByte((y * cmap.getcx() + x) * cmap.getcc() + 1) / 255.0f;
704         r.z = cmap.getByte((y * cmap.getcx() + x) * cmap.getcc() + 2) / 255.0f;
705         return r;
706     }
707 
708     ///
709     /// @brief Returns the road surface type corresponding to the given position
710     ///  in the terrain.
711     /// @note The height component Z is ignored.
712     /// @todo Should remove paranoid clampings?
713     /// @todo Should actually measure performance of float vs int. (int should be intrinsecally faster)
714     /// @param [in] pos   Position in the terrain.
715     /// @returns Terrain type.
716     ///
getRoadSurface(const vec3f & pos)717     TerrainType getRoadSurface(const vec3f &pos) const
718     {
719         if (tmap.getData() == nullptr)
720             return TerrainType::Unknown;
721 #if 0
722         const float ms = getMapSize();
723         float px = pos.x;
724         float py = pos.y;
725 #else
726         const int ms = static_cast<int> (getMapSize());
727         int px = static_cast<int> (pos.x);
728         int py = static_cast<int> (pos.y);
729 #endif
730         if (px >= ms)
731         {
732             do
733                 px -= ms;
734             while (px > ms);
735         }
736         else
737         if (px < 0)
738         {
739             do
740                 px += ms;
741             while (px < 0);
742         }
743 
744         if (py >= ms)
745         {
746             do
747                 py -= ms;
748             while (py > ms);
749         }
750         else
751         if (py < 0)
752         {
753             do
754                 py += ms;
755             while (py < 0);
756         }
757 
758         long int x = std::lround(px * tmap.getcx() / getMapSize());
759         long int y = std::lround(py * tmap.getcy() / getMapSize());
760         rgbcolor temp;
761 
762         CLAMP_UPPER(x, tmap.getcx() - 1);
763         CLAMP_UPPER(y, tmap.getcy() - 1);
764         temp.r = tmap.getByte((y * tmap.getcx() + x) * tmap.getcc() + 0);
765         temp.g = tmap.getByte((y * tmap.getcx() + x) * tmap.getcc() + 1);
766         temp.b = tmap.getByte((y * tmap.getcx() + x) * tmap.getcc() + 2);
767         return PUtil::decideRoadSurface(temp);
768     }
769 
770   ///
771   /// @brief get the information about a contact point (its coordinates and normal) with the ground
772   ///
getContactInfo(ContactInfo & tci)773   void getContactInfo(ContactInfo &tci) {
774     float x = tci.pos.x * scale_hz_inv;
775     int xi = (int)x;
776     if (x < 0.0) xi--;
777     x -= (float)xi;
778     int xiw = xi & totmask, xiw2 = (xi+1) & totmask;
779 
780     float y = tci.pos.y * scale_hz_inv;
781     int yi = (int)y;
782     if (y < 0.0) yi--;
783     y -= (float)yi;
784     int yiw = yi & totmask, yiw2 = (yi+1) & totmask;
785 
786     float *data = &hmap[0];
787     const int cx = totsize;
788 
789     float xv1,xv2;
790     if (y > 0.0) {
791       if (y < 1.0) {
792         if (x < y) {
793           tci.normal.x = data[yiw2*cx+xiw] - data[yiw2*cx+xiw2];
794           tci.normal.y = data[yiw*cx+xiw] - data[yiw2*cx+xiw];
795           xv1 = data[yiw*cx+xiw];
796           xv2 = INTERP(data[yiw2*cx+xiw],data[yiw2*cx+xiw2],x/y);
797         } else {
798           tci.normal.x = data[yiw*cx+xiw] - data[yiw*cx+xiw2];
799           tci.normal.y = data[yiw*cx+xiw2] - data[yiw2*cx+xiw2];
800           xv1 = INTERP(data[yiw*cx+xiw],data[yiw*cx+xiw2],(x-y)/(1.0-y));
801           xv2 = data[yiw2*cx+xiw2];
802         }
803         tci.pos.z = INTERP(xv1,xv2,y);
804       } else {
805         tci.normal.x = data[yiw2*cx+xiw] - data[yiw2*cx+xiw2];
806         tci.normal.y = data[yiw*cx+xiw] - data[yiw2*cx+xiw];
807         tci.pos.z = INTERP(data[yiw2*cx+xiw],data[yiw2*cx+xiw2],x);
808       }
809     } else {
810       tci.normal.x = data[yiw*cx+xiw] - data[yiw*cx+xiw2];
811       tci.normal.y = data[yiw*cx+xiw2] - data[yiw2*cx+xiw2];
812       tci.pos.z = INTERP(data[yiw*cx+xiw],data[yiw*cx+xiw2],x);
813     }
814     tci.normal.z = scale_hz;
815     tci.normal.normalize();
816   }
817 
getHeight(float x,float y)818   float getHeight(float x, float y) {
819     return getInterp(x, y, &hmap[0]);
820   }
821 
getFoliageLevel(float x,float y)822   float getFoliageLevel(float x, float y) {
823     return getInterp(x, y, &fmap[0]);
824   }
825 
getHUDMapTexture()826   PTexture *getHUDMapTexture() { return tex_hud_map; }
827 
getMapSize()828   float getMapSize() const { return totsize * scale_hz; }
829 };
830 
831 
832 
833 
834