1
2
3 #include "tgl.h"
4
5 #include "tconvert.h"
6 #include "tofflinegl.h"
7 #include "trop.h"
8 #include "timage_io.h"
9 #include "tcurves.h"
10
11 #ifndef __sgi
12 #ifdef _WIN32
13 #include <cstdlib>
14 #include <GL/glut.h>
15 #elif defined(LINUX) || defined(FREEBSD)
16 #include <GL/glut.h>
17 #else
18 #include <GLUT/glut.h>
19 #endif
20 #endif
21
22 #if defined(MACOSX) || defined(LINUX) || defined(FREEBSD)
23 #include <QGLContext>
24 #endif
25
26 //#include "tthread.h"
27
28 #undef SCALE_BY_GLU
29 //#undef NEW_DRAW_TEXT
30
31 //-----------------------------------------------------------------------------
32
33 namespace {
34
35 // GLUquadric* localDisk=0;
36
37 /*
38 Find the number of slices in function of radius size.
39 \par radius of circle
40 \par size of pixel
41 \ret number of division to obtain a circle
42 */
computeSlices(double radius,double pixelSize=1.0)43 int computeSlices(double radius, double pixelSize = 1.0) {
44 if (radius < 0) return 2;
45
46 double thetaStep;
47 double temp = pixelSize * 0.5 / radius;
48
49 if (fabs(1.0 - temp) <= 1)
50 thetaStep = acos(1.0 - temp);
51 else
52 thetaStep = M_PI_4;
53
54 assert(thetaStep != 0.0);
55
56 int numberOfSlices = (int)(M_2PI / thetaStep);
57
58 return numberOfSlices != 0 ? numberOfSlices : 2;
59 }
60 } // end of unnamed namespace
61
62 //-----------------------------------------------------------------------------
63
tglGetPixelSize2()64 double tglGetPixelSize2() {
65 double mat[16];
66 glMatrixMode(GL_MODELVIEW);
67 glGetDoublev(GL_MODELVIEW_MATRIX, mat);
68
69 double det = fabs(mat[0] * mat[5] - mat[1] * mat[4]);
70 if (det < TConsts::epsilon) det = TConsts::epsilon;
71 return 1.0 / det;
72 }
73
74 //-----------------------------------------------------------------------------
75
tglGetTextWidth(const std::string & s,void * font)76 double tglGetTextWidth(const std::string &s, void *font) {
77 double factor = 0.07;
78 double w = 0;
79 for (int i = 0; i < (int)s.length(); i++) w += glutStrokeWidth(font, s[i]);
80 return w * factor;
81 }
82
83 //-----------------------------------------------------------------------------
84
tglDrawText(const TPointD & p,const std::string & s,void * character)85 void tglDrawText(const TPointD &p, const std::string &s, void *character) {
86 #ifndef __sgi
87 glPushMatrix();
88 glTranslated(p.x, p.y, 0);
89 double factor = 0.07;
90 glScaled(factor, factor, factor);
91 for (int i = 0; i < (int)s.size(); i++) glutStrokeCharacter(character, s[i]);
92 glPopMatrix();
93 #else
94 assert("Not Yet Implemented" && 0);
95 std::cout << s << std::endl;
96 #endif
97 }
98
99 //-----------------------------------------------------------------------------
100
tglDrawText(const TPointD & p,const std::wstring & s,void * character)101 void tglDrawText(const TPointD &p, const std::wstring &s, void *character) {
102 #ifndef __sgi
103 glPushMatrix();
104 glTranslated(p.x, p.y, 0);
105 double factor = 0.07;
106 glScaled(factor, factor, factor);
107 for (int i = 0; i < (int)s.size(); i++) glutStrokeCharacter(character, s[i]);
108 glPopMatrix();
109 #else
110 assert("Not Yet Implemented" && 0);
111 std::cout << s << std::endl;
112 #endif
113 }
114
115 //-----------------------------------------------------------------------------
116
tglDrawSegment(const TPointD & p1,const TPointD & p2)117 void tglDrawSegment(const TPointD &p1, const TPointD &p2) {
118 glBegin(GL_LINES);
119 tglVertex(p1);
120 tglVertex(p2);
121 glEnd();
122 }
123
124 //-----------------------------------------------------------------------------
125
tglDrawCircle(const TPointD & center,double radius)126 void tglDrawCircle(const TPointD ¢er, double radius) {
127 if (radius <= 0) return;
128
129 double pixelSize = 1;
130 int slices = 60;
131
132 if (slices <= 0) slices = computeSlices(radius, pixelSize) >> 1;
133
134 double step = M_PI / slices;
135 double step2 = 2.0 * step;
136
137 double cos_t, sin_t, cos_ts, sin_ts, t;
138
139 glPushMatrix();
140 glTranslated(center.x, center.y, 0.0);
141 glBegin(GL_LINES);
142
143 cos_t = radius /* *1.0*/;
144 sin_t = 0.0;
145 for (t = 0; t + step < M_PI_2; t += step2) {
146 cos_ts = radius * cos(t + step);
147 sin_ts = radius * sin(t + step);
148
149 glVertex2f(cos_t, sin_t);
150 glVertex2f(cos_ts, sin_ts);
151
152 glVertex2f(-cos_t, sin_t);
153 glVertex2f(-cos_ts, sin_ts);
154
155 glVertex2f(-cos_t, -sin_t);
156 glVertex2f(-cos_ts, -sin_ts);
157
158 glVertex2f(cos_t, -sin_t);
159 glVertex2f(cos_ts, -sin_ts);
160
161 cos_t = cos_ts;
162 sin_t = sin_ts;
163 }
164
165 cos_ts = 0.0;
166 sin_ts = radius /* *1.0*/;
167
168 glVertex2f(cos_t, sin_t);
169 glVertex2f(cos_ts, sin_ts);
170
171 glVertex2f(-cos_t, sin_t);
172 glVertex2f(-cos_ts, sin_ts);
173
174 glVertex2f(-cos_t, -sin_t);
175 glVertex2f(-cos_ts, -sin_ts);
176
177 glVertex2f(cos_t, -sin_t);
178 glVertex2f(cos_ts, -sin_ts);
179
180 glEnd();
181 glPopMatrix();
182 }
183
184 //-----------------------------------------------------------------------------
185
tglDrawDisk(const TPointD & c,double r)186 void tglDrawDisk(const TPointD &c, double r) {
187 if (r <= 0) return;
188
189 double pixelSize = 1;
190 int slices = 60;
191
192 if (slices <= 0) slices = computeSlices(r, pixelSize) >> 1;
193
194 glPushMatrix();
195 glTranslated(c.x, c.y, 0.0);
196 GLUquadric *quadric = gluNewQuadric();
197 gluDisk(quadric, 0, r, slices, 1);
198 gluDeleteQuadric(quadric);
199 glPopMatrix();
200 }
201
202 //-----------------------------------------------------------------------------
203
tglDrawRect(const TRectD & rect)204 void tglDrawRect(const TRectD &rect) {
205 glBegin(GL_LINE_LOOP);
206 tglVertex(rect.getP00());
207 tglVertex(rect.getP10());
208 tglVertex(rect.getP11());
209 tglVertex(rect.getP01());
210 glEnd();
211 }
212
213 //-----------------------------------------------------------------------------
214
tglFillRect(const TRectD & rect)215 void tglFillRect(const TRectD &rect) {
216 glBegin(GL_POLYGON);
217 tglVertex(rect.getP00());
218 tglVertex(rect.getP10());
219 tglVertex(rect.getP11());
220 tglVertex(rect.getP01());
221 glEnd();
222 }
223 //-----------------------------------------------------------------------------
224
tglRgbOnlyColorMask()225 void tglRgbOnlyColorMask() {
226 tglMultColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
227 tglEnableBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
228 }
229
230 //-----------------------------------------------------------------------------
231
tglAlphaOnlyColorMask()232 void tglAlphaOnlyColorMask() {
233 tglMultColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
234 tglEnableBlending(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
235 }
236
237 //-----------------------------------------------------------------------------
238
tglEnableBlending(GLenum src,GLenum dst)239 void tglEnableBlending(GLenum src, GLenum dst) {
240 glEnable(GL_BLEND);
241 glBlendFunc(src, dst);
242 }
243
244 //-----------------------------------------------------------------------------
245
tglEnableLineSmooth(bool enable,double lineSize)246 void tglEnableLineSmooth(bool enable, double lineSize) {
247 if (enable) {
248 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
249 glEnable(GL_LINE_SMOOTH);
250 glLineWidth(lineSize);
251 } else
252 glDisable(GL_LINE_SMOOTH);
253 }
254
255 //-----------------------------------------------------------------------------
256
tglEnablePointSmooth(double pointSize)257 void tglEnablePointSmooth(double pointSize) {
258 glEnable(GL_BLEND);
259 glPointSize(pointSize);
260 }
261
262 //-----------------------------------------------------------------------------
263
tglGetColorMask(GLboolean & red,GLboolean & green,GLboolean & blue,GLboolean & alpha)264 void tglGetColorMask(GLboolean &red, GLboolean &green, GLboolean &blue,
265 GLboolean &alpha) {
266 GLboolean channels[4];
267 glGetBooleanv(GL_COLOR_WRITEMASK, &channels[0]);
268 red = channels[0], green = channels[1], blue = channels[2],
269 alpha = channels[3];
270 }
271
272 //-----------------------------------------------------------------------------
273
tglMultColorMask(GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha)274 void tglMultColorMask(GLboolean red, GLboolean green, GLboolean blue,
275 GLboolean alpha) {
276 GLboolean channels[4];
277 glGetBooleanv(GL_COLOR_WRITEMASK, &channels[0]);
278 glColorMask(red && channels[0], green && channels[1], blue && channels[2],
279 alpha && channels[3]);
280 }
281
282 //============================================================================
283
284 namespace {
285 //============================================================================
286
287 class GlFontManager {
288 GlFontManager();
289
290 public:
291 ~GlFontManager();
292 static GlFontManager *instance();
293 bool setFont(void *font = GLUT_BITMAP_TIMES_ROMAN_10);
294 void drawText(/*const TRectD bBox,*/
295 std::wstring wtext /*,
296 TDimensionD scale = TDimensionD(1.0, 1.0)*/);
297
298 private:
299 static GlFontManager *m_instance;
300
301 // font font_height
302 // | |
303 std::map<void *, double> m_fonts;
304 std::vector<TRectD> m_charsBBox;
305 void *m_currentFont;
306 TRaster32P m_fontTexture;
307 GLuint m_base;
308 };
309
310 //----------------------------------------------------------------------------
311
312 GlFontManager *GlFontManager::m_instance = 0L;
313
314 //----------------------------------------------------------------------------
315
GlFontManager()316 GlFontManager::GlFontManager() : m_currentFont(0L), m_base(0) {
317 m_fonts.insert(std::make_pair(GLUT_BITMAP_8_BY_13, 13.0));
318 m_fonts.insert(std::make_pair(GLUT_BITMAP_9_BY_15, 15.0));
319 m_fonts.insert(std::make_pair(GLUT_BITMAP_TIMES_ROMAN_10, 10.0));
320 m_fonts.insert(std::make_pair(GLUT_BITMAP_TIMES_ROMAN_24, 24.0));
321 m_fonts.insert(std::make_pair(GLUT_BITMAP_HELVETICA_10, 10.0));
322 m_fonts.insert(std::make_pair(GLUT_BITMAP_HELVETICA_12, 12.0));
323 m_fonts.insert(std::make_pair(GLUT_BITMAP_HELVETICA_18, 18.0));
324 bool ok = setFont();
325 assert(ok);
326 }
327
328 //----------------------------------------------------------------------------
329
~GlFontManager()330 GlFontManager::~GlFontManager() { m_instance = 0L; }
331
332 //----------------------------------------------------------------------------
333
instance()334 GlFontManager *GlFontManager::instance() {
335 if (!m_instance) m_instance = new GlFontManager();
336
337 return m_instance;
338 }
339
340 //----------------------------------------------------------------------------
341
setFont(void * font)342 bool GlFontManager::setFont(void *font) {
343 // cerca il font scelto nella mappa dei fonts conosciuti
344 std::map<void *, double>::iterator it = m_fonts.find(font);
345
346 // se e' stato trovato
347 if (it != m_fonts.end()) {
348 m_currentFont = font;
349
350 glPushAttrib(GL_ALL_ATTRIB_BITS);
351 glPushMatrix();
352 m_base = glGenLists(256);
353 glListBase(m_base);
354 int i = 0;
355 for (; i < 256; ++i) {
356 glNewList(m_base + i, GL_COMPILE);
357 glutStrokeCharacter(GLUT_STROKE_ROMAN, i);
358 // glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, i);
359 glEndList();
360 }
361 glPopAttrib();
362 glPopMatrix();
363 return true;
364 }
365 return false;
366 }
367
368 //----------------------------------------------------------------------------
369
drawText(std::wstring wtext)370 void GlFontManager::drawText(/*const TRectD bBox,*/
371 std::wstring wtext /*,
372 TDimensionD scale*/) {
373 if (!m_currentFont) return;
374
375 std::string text = ::to_string(wtext);
376 const char *textString = text.c_str();
377 glListBase(m_base);
378 /*
379 glPushMatrix();
380 glTranslated(bBox.x0, bBox.y0, 0.0);
381 glScaled(scale.lx*0.07, scale.ly*0.07, 1.0);
382 */
383 glCallLists((GLuint)strlen(textString), GL_BYTE, textString);
384 /*glPopMatrix();*/
385 }
386
387 //============================================================================
388
389 } // anonymous namespace
390
391 //============================================================================
392
tglDraw(const TCubic & cubic,int precision,GLenum pointOrLine)393 void tglDraw(const TCubic &cubic, int precision, GLenum pointOrLine) {
394 CHECK_ERRORS_BY_GL;
395 assert(pointOrLine == GL_POINT || pointOrLine == GL_LINE);
396 float ctrlPts[4][3];
397
398 ctrlPts[0][0] = cubic.getP0().x;
399 ctrlPts[0][1] = cubic.getP0().y;
400 ctrlPts[0][2] = 0.0;
401
402 ctrlPts[1][0] = cubic.getP1().x;
403 ctrlPts[1][1] = cubic.getP1().y;
404 ctrlPts[1][2] = 0.0;
405
406 ctrlPts[2][0] = cubic.getP2().x;
407 ctrlPts[2][1] = cubic.getP2().y;
408 ctrlPts[2][2] = 0.0;
409
410 ctrlPts[3][0] = cubic.getP3().x;
411 ctrlPts[3][1] = cubic.getP3().y;
412 ctrlPts[3][2] = 0.0;
413
414 CHECK_ERRORS_BY_GL;
415 glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlPts[0][0]);
416 CHECK_ERRORS_BY_GL;
417 glEnable(GL_MAP1_VERTEX_3);
418 CHECK_ERRORS_BY_GL;
419 glMapGrid1f(precision, 0.0, 1.0);
420 CHECK_ERRORS_BY_GL;
421 glEvalMesh1(pointOrLine, 0, precision);
422 CHECK_ERRORS_BY_GL;
423 }
424
425 //-----------------------------------------------------------------------------
426
tglDraw(const TRectD & rect,const std::vector<TRaster32P> & textures,bool blending)427 void tglDraw(const TRectD &rect, const std::vector<TRaster32P> &textures,
428 bool blending) {
429 double pixelSize2 = tglGetPixelSize2();
430 // level e' la minore potenza di 2 maggiore di sqrt(pixelSize2)
431 unsigned int level = 1;
432 while (pixelSize2 * level * level <= 1.0) level <<= 1;
433
434 unsigned int texturesCount = (int)textures.size();
435 if (level > texturesCount) level = texturesCount;
436
437 level = texturesCount - level;
438
439 tglDraw(rect, textures[level], blending);
440 }
441
442 //-------------------------------------------------------------------
443
tglDraw(const TRectD & rect,const TRaster32P & tex,bool blending)444 void tglDraw(const TRectD &rect, const TRaster32P &tex, bool blending) {
445 CHECK_ERRORS_BY_GL;
446 glPushAttrib(GL_ALL_ATTRIB_BITS);
447 if (blending) {
448 glEnable(GL_BLEND);
449 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
450 }
451
452 unsigned int texWidth = 1;
453 unsigned int texHeight = 1;
454
455 while (texWidth < (unsigned int)tex->getLx()) texWidth = texWidth << 1;
456
457 while (texHeight < (unsigned int)tex->getLy()) texHeight = texHeight << 1;
458
459 double lwTex = 1.0;
460 double lhTex = 1.0;
461
462 TRaster32P texture;
463 unsigned int texLx = (unsigned int)tex->getLx();
464 unsigned int texLy = (unsigned int)tex->getLy();
465
466 if (texWidth != texLx || texHeight != texLy) {
467 texture = TRaster32P(texWidth, texHeight);
468 texture->fill(TPixel32(0, 0, 0, 0));
469 texture->copy(tex);
470 lwTex = (texLx) / (double)(texWidth);
471 lhTex = (texLy) / (double)(texHeight);
472 if (lwTex > 1.0) lwTex = 1.0;
473 if (lhTex > 1.0) lhTex = 1.0;
474 } else
475 texture = tex;
476 GLenum fmt =
477 #if defined(TNZ_MACHINE_CHANNEL_ORDER_BGRM)
478 GL_BGRA_EXT;
479 #elif defined(TNZ_MACHINE_CHANNEL_ORDER_MBGR)
480 GL_ABGR_EXT;
481 #elif defined(TNZ_MACHINE_CHANNEL_ORDER_RGBM)
482 GL_RGBA;
483 #elif defined(TNZ_MACHINE_CHANNEL_ORDER_MRGB)
484 GL_BGRA;
485 #else
486 // Error PLATFORM NOT SUPPORTED
487 #error "unknown channel order!"
488 #endif
489
490 // Generate a texture id and bind it.
491 GLuint texId;
492 glGenTextures(1, &texId);
493
494 glBindTexture(GL_TEXTURE_2D, texId);
495
496 glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->getWrap());
497
498 texture->lock();
499 glTexImage2D(GL_TEXTURE_2D, 0, 4, texWidth, texHeight, 0, fmt,
500 #ifdef TNZ_MACHINE_CHANNEL_ORDER_MRGB
501 GL_UNSIGNED_INT_8_8_8_8_REV,
502 #else
503 GL_UNSIGNED_BYTE,
504 #endif
505 texture->getRawData());
506
507 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
508 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
509
510 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
511 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
512
513 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
514 glEnable(GL_TEXTURE_2D);
515
516 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
517
518 double rectLx = rect.getLx();
519 double rectLy = rect.getLy();
520
521 tglColor(TPixel32(0, 0, 0, 0));
522
523 glPushMatrix();
524
525 glTranslated(rect.x0, rect.y0, 0.0);
526 glBegin(GL_POLYGON);
527
528 glTexCoord2d(0, 0);
529 tglVertex(TPointD(0.0, 0.0));
530
531 glTexCoord2d(lwTex, 0);
532 tglVertex(TPointD(rectLx, 0.0));
533
534 glTexCoord2d(lwTex, lhTex);
535 tglVertex(TPointD(rectLx, rectLy));
536
537 glTexCoord2d(0, lhTex);
538 tglVertex(TPointD(0.0, rectLy));
539
540 glEnd();
541 glDisable(GL_TEXTURE_2D);
542
543 glPopMatrix();
544 glPopAttrib();
545
546 // Delete texture
547 glDeleteTextures(1, &texId);
548
549 texture->unlock();
550 }
551
552 //-----------------------------------------------------------------------------
553
tglBuildMipmaps(std::vector<TRaster32P> & rasters,const TFilePath & filepath)554 void tglBuildMipmaps(std::vector<TRaster32P> &rasters,
555 const TFilePath &filepath) {
556 assert(rasters.size() > 0);
557 TRop::ResampleFilterType resampleFilter = TRop::ClosestPixel;
558 TRasterP ras;
559 TImageReader::load(filepath, ras);
560 int rasLx = ras->getLx();
561 int rasLy = ras->getLy();
562
563 int lx = 1;
564 while (lx < rasLx) lx <<= 1;
565
566 int ly = 1;
567 while (ly < rasLy) ly <<= 1;
568
569 TRaster32P ras2(lx, ly);
570 double sx = (double)lx / (double)ras->getLx();
571 double sy = (double)ly / (double)ras->getLy();
572 #ifndef SCALE_BY_GLU
573 TRop::resample(ras2, ras, TScale(sx, sy), resampleFilter);
574 #else
575 ras->lock();
576 gluScaleImage(GL_RGBA, ras->getLx(), ras->getLy(), GL_UNSIGNED_BYTE,
577 ras->getRawData(), lx, ly, GL_UNSIGNED_BYTE,
578 ras2->getRawData());
579 ras->unlock();
580 #endif
581
582 rasters[0] = ras2;
583 int ras2Lx = ras2->getLx();
584 int ras2Ly = ras2->getLy();
585 for (int i = 1; i < (int)rasters.size(); ++i) {
586 lx >>= 1;
587 ly >>= 1;
588 if (lx < 1) lx = 1;
589 if (ly < 1) ly = 1;
590 rasters[i] = TRaster32P(lx, ly);
591 sx = (double)lx / (double)ras2Lx;
592 sy = (double)ly / (double)ras2Ly;
593 rasters[i] = TRaster32P(lx, ly);
594 #ifndef SCALE_BY_GLU
595 TRop::resample(rasters[i], ras2, TScale(sx, sy), resampleFilter);
596 #else
597 ras2->lock();
598 gluScaleImage(GL_RGBA, ras->getLx(), ras->getLy(), GL_UNSIGNED_BYTE,
599 ras2->getRawData(), lx, ly, GL_UNSIGNED_BYTE,
600 rasters[i]->getRawData());
601 ras2->unlock();
602 #endif
603 }
604 }
605
606 //-----------------------------------------------------------------------------
607 // Forse si potrebbe togliere l'ifdef ed usare QT
608 #if defined(_WIN32)
609
tglGetCurrentContext()610 TGlContext tglGetCurrentContext() {
611 return std::make_pair(wglGetCurrentDC(), wglGetCurrentContext());
612 }
613
tglMakeCurrent(TGlContext context)614 void tglMakeCurrent(TGlContext context) {
615 wglMakeCurrent(context.first, context.second);
616 }
617
tglDoneCurrent(TGlContext)618 void tglDoneCurrent(TGlContext) { wglMakeCurrent(NULL, NULL); }
619
620 #elif defined(LINUX) || defined(FREEBSD) || defined(__sgi) || defined(MACOSX)
621
tglGetCurrentContext()622 TGlContext tglGetCurrentContext() {
623 return reinterpret_cast<TGlContext>(
624 const_cast<QGLContext *>(QGLContext::currentContext()));
625
626 // (Daniele) I'm not sure why QGLContext::currentContext() returns
627 // const. I think it shouldn't, and guess (hope) this is safe...
628 }
629
tglMakeCurrent(TGlContext context)630 void tglMakeCurrent(TGlContext context) {
631 if (context)
632 reinterpret_cast<QGLContext *>(context)->makeCurrent();
633 else
634 tglDoneCurrent(tglGetCurrentContext());
635 }
636
tglDoneCurrent(TGlContext context)637 void tglDoneCurrent(TGlContext context) {
638 if (context) reinterpret_cast<QGLContext *>(context)->doneCurrent();
639 }
640
641 #else
642 #error "unknown platform!"
643 #endif
644
645 //-----------------------------------------------------------------------------
646 // End Of File
647 //-----------------------------------------------------------------------------
648