1 /*---------------------------------------------------------------------------*
2 Project: Dolphin/Revolution gx demo
3 File: tf-mirror.c
4
5 Copyright 1998-2006 Nintendo. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain
8 proprietary information of Nintendo of America Inc. and/or Nintendo
9 Company Ltd., and are protected by Federal copyright law. They may
10 not be disclosed to third parties or copied or duplicated in any form,
11 in whole or in part, without the prior written consent of Nintendo.
12 *---------------------------------------------------------------------------*/
13
14 #include <demo.h>
15 #include <math.h>
16
17 /*---------------------------------------------------------------------------*
18 Defines
19 *---------------------------------------------------------------------------*/
20
21 #define SIDE 35
22 #define MIRRORHT 175.0f
23 #define MIRRORWD 150.0f
24 #define NORM (sqrtf(3.0f)/3.0f)
25
26 #define ASPECT (10.0f/7.0f)
27 #define FOVY (45.0f)
28 #define XCENTER (320)
29 #define YCENTER (224)
30
31 #define PI 3.1415926535f
32
33 #define Clamp(val,min,max) \
34 ((val) = (((val) < (min)) ? (min) : ((val) > (max)) ? (max) : (val)))
35
36 /*---------------------------------------------------------------------------*
37 Forward references
38 *---------------------------------------------------------------------------*/
39
40 void main ( void );
41
42 static void CameraInit ( void );
43 static void DrawInit ( void );
44 static void DrawTick ( void );
45 static void AnimTick ( void );
46
47 static void DrawScene ( GXBool mirror );
48 static void GetMirrorMv ( VecPtr trans, VecPtr rot, Mtx mv );
49 static void DrawMirror ( void );
50 static void SendVertex ( u16 posIndex, u16 texCoordIndex );
51
52 static void PrintIntro ( void );
53
54 /*---------------------------------------------------------------------------*
55 Global variables
56 *---------------------------------------------------------------------------*/
57
58 Mtx v; // viewing (camera) matrix
59 u32 rot = 45; // cube rotation
60 Vec mirrRot = { 0.0f, -45.0f, 0.0f }; // mirror rotation on each axis
61 Vec mirrTrans = { 0, 0, 0 }; // mirror translation
62
63 u8 CurrentControl; // control for reflected texture scale
64 u8 VpScale = 1; // actual value for reflected texture scale
65 u8 DoRotation = 1; // controls cube rotation
66 u8 testing = 0; // if true, show only the computed reflection
67
68 GXTexObj face_obj; // texture object for cube faces
69 GXTexObj mirr_obj; // texture object for mirror contents
70
71 u8* MirrTexData = NULL; // texture data for mirror contents
72
73 f32 FloatVert[] ATTRIBUTE_ALIGN(32) = { // cube verts
74 -SIDE, SIDE, -SIDE,
75 -SIDE, SIDE, SIDE,
76 -SIDE, -SIDE, SIDE,
77 -SIDE, -SIDE, -SIDE,
78 SIDE, SIDE, -SIDE,
79 SIDE, -SIDE, -SIDE,
80 SIDE, -SIDE, SIDE,
81 SIDE, SIDE, SIDE,
82 };
83
84 f32 MirrorVert[] = { // mirror verts
85 -MIRRORWD, 0.0f, 0.0f,
86 -MIRRORWD, MIRRORHT, 0.0f,
87 MIRRORWD, MIRRORHT, 0.0f,
88 MIRRORWD, 0.0f, 0.0f,
89 };
90
91 f32 FloatNorm[] ATTRIBUTE_ALIGN(32) = { // cube normals; adjusted by app
92 -1, 1, -1,
93 -1, 1, 1,
94 -1, -1, 1,
95 -1, -1, -1,
96 1, 1, -1,
97 1, -1, -1,
98 1, -1, 1,
99 1, 1, 1,
100 };
101
102 f32 FloatTex[] ATTRIBUTE_ALIGN(32) = { // cube face texcoords
103 0.0F, 1.0F,
104 0.0F, 0.0F,
105 1.0F, 0.0F,
106 1.0F, 1.0F,
107 };
108
109 /*---------------------------------------------------------------------------*
110 Application main loop
111 *---------------------------------------------------------------------------*/
112
main(void)113 void main ( void )
114 {
115 DEMOInit(NULL);
116 DrawInit(); // Define my vertex formats and set array pointers.
117
118 PrintIntro();
119
120 DEMOPadRead(); // Read the joystick for this frame
121
122 // While the quit button is not pressed
123 while(!(DEMOPadGetButton(0) & PAD_BUTTON_MENU))
124 {
125 DEMOPadRead(); // Read the joystick for this frame
126
127 // Do animation based on input
128 AnimTick();
129 DEMOBeforeRender();
130
131 DrawTick(); // Draw the scene
132
133 DEMODoneRender();
134 }
135
136 OSHalt("End of test");
137 }
138
139 /*---------------------------------------------------------------------------*
140 Functions
141 *---------------------------------------------------------------------------*/
142
143
144 /*---------------------------------------------------------------------------*
145 Name: CameraInit
146
147 Description: Initialize the projection matrix and load into hardware.
148 Initialize camera matrix.
149
150 Arguments: none
151
152 Returns: none
153 *---------------------------------------------------------------------------*/
CameraInit(void)154 static void CameraInit ( void )
155 {
156 Mtx44 p;
157 Vec camPt = {0.0F, 50.0F, 650.0F};
158 Vec up = {0.0F, 1.0F, 0.0F};
159 Vec origin = {0.0F, 0.0F, 0.0F};
160
161 MTXPerspective(p, FOVY, ASPECT, 100, 2000);
162
163 GXSetProjection(p, GX_PERSPECTIVE);
164
165 MTXLookAt(v, &camPt, &up, &origin);
166 }
167
168 /*---------------------------------------------------------------------------*
169 Name: DrawInit
170
171 Description: Graphics initialization function for the current model.
172
173 Arguments: none
174
175 Returns: none
176 *---------------------------------------------------------------------------*/
DrawInit(void)177 static void DrawInit( void )
178 {
179 TPLPalettePtr tpl = 0;
180 u32 i;
181 GXColor blue_opa = {0x00, 0x00, 0xa0, 0xff};
182
183 CameraInit(); // Initialize the camera.
184
185 // Set up VAT, array base pointers
186
187 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
188 GXSetArray(GX_VA_TEX0, FloatTex, 8);
189
190 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
191 GXSetArray(GX_VA_NRM, FloatNorm, 12);
192
193 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
194 GXSetArray(GX_VA_POS, FloatVert, 12);
195
196 // Load cube texture
197
198 TPLGetPalette(&tpl, "gxTests/tf-02.tpl");
199
200 TPLGetGXTexObjFromPalette(tpl, &face_obj, 0);
201
202 // Disable lighting, set up color channel
203
204 GXSetChanCtrl(
205 GX_COLOR0A0,
206 GX_DISABLE, // use material color
207 GX_SRC_REG, // --from material register
208 GX_SRC_REG, // not used
209 GX_LIGHT0,
210 GX_DF_NONE,
211 GX_AF_NONE );
212 GXSetChanMatColor(
213 GX_COLOR0A0,
214 blue_opa );
215
216 // Scale normals to unit length (not really necessary, but a good idea)
217 for(i = 0; i < 24; i++)
218 {
219 FloatNorm[i] *= NORM;
220 }
221 // Important: data must be in main memory for GP to see it
222 DCFlushRange( (void *) FloatNorm, sizeof(f32)*24 );
223
224 // Allocate mirror texture buffer
225 MirrTexData = (u8*)MEMAllocFromAllocator(&DemoAllocator1,
226 GXGetTexBufferSize(
227 XCENTER*2, // maximum screen width
228 YCENTER*2, // maximum screen height
229 GX_TF_I8,
230 GX_FALSE, // no mipmap
231 0) );
232 }
233
234 /*---------------------------------------------------------------------------*
235 Name: DrawTick
236
237 Description: Draw the current model once.
238
239 Arguments: none
240
241 Returns: none
242 *---------------------------------------------------------------------------*/
DrawTick(void)243 static void DrawTick( void )
244 {
245 u32 i; // misc index
246 f32 p[7]; // current projection matrix
247 f32 vp[6]; // current viewport
248 Vec sc[4]; // screen coords of mirror
249 s16 minx, maxx; // x bounds of mirror
250 s16 miny, maxy; // y bounds of mirror
251 u16 width; // mirror width
252 u16 height; // mirror height
253 f32 x1, x2, y1, y2; // used for texture projection matrix calculation
254 Mtx mv; // modelview mtx
255 Mtx mt; // texture proj mtx
256 static u16 count = 0; // drawsync counter
257
258 //
259 // Compute bounding box for mirrored render
260 //
261 GXGetProjectionv(p);
262 GXGetViewportv(vp);
263
264 vp[2] /= VpScale; // scale VP width
265 vp[3] /= VpScale; // scale VP height
266
267 // Construct a matrix for viewing the mirror
268 GetMirrorMv( &mirrTrans, &mirrRot, mv );
269 MTXConcat(v, mv, mv); // concatenate with viewing matrix
270
271 // Project mirror corners to screen space
272 for (i = 0; i < 4; i++)
273 {
274 GXProject(MirrorVert[i*3], MirrorVert[i*3+1], MirrorVert[i*3+2],
275 mv, p, vp,
276 &sc[i].x, &sc[i].y, &sc[i].z);
277 }
278
279 // Get bounding box of texture to copy
280 minx = maxx = (s16)sc[0].x;
281 miny = maxy = (s16)sc[0].y;
282 for (i = 1; i < 4; i++)
283 {
284 if (minx > (s16)sc[i].x)
285 minx = (s16)sc[i].x;
286 if (maxx < (s16)sc[i].x)
287 maxx = (s16)sc[i].x;
288 if (miny > (s16)sc[i].y)
289 miny = (s16)sc[i].y;
290 if (maxy < (s16)sc[i].y)
291 maxy = (s16)sc[i].y;
292 }
293
294 // "Clip" against screen boundaries
295 if (miny < 0)
296 miny = 0;
297 if (minx < 0)
298 minx = 0;
299 if (maxx > XCENTER*2-1)
300 maxx = XCENTER*2-1;
301 if (maxy > YCENTER*2-1)
302 maxy = YCENTER*2-1;
303
304 // Adjust coordinates to be a multiple of 2 (necessary for copy)
305 minx = (s16) (minx & (~1)); // round down
306 miny = (s16) (miny & (~1));
307 maxx = (s16) (maxx | ( 1)); // round up
308 maxy = (s16) (maxy | ( 1));
309
310 // Get width and height of texture
311 width = (u16)(maxx - minx + 1);
312 height = (u16)(maxy - miny + 1);
313
314 // Compute texture projection matrix
315 // First, return min/max's to unit projection space
316 x1 = ((f32) minx / (XCENTER/VpScale) ) - 1.0f;
317 x2 = ((f32) maxx / (XCENTER/VpScale) ) - 1.0f;
318 y1 = ((f32) miny / (YCENTER/VpScale) ) - 1.0f;
319 y2 = ((f32) maxy / (YCENTER/VpScale) ) - 1.0f;
320 // Adjust such that 0-1 range encompasses viewport used to make texture
321 MTXLightPerspective(mt, FOVY, ASPECT,
322 1.0f/(x2-x1), -1.0f/(y2-y1),
323 -x1/(x2-x1), -y1/(y2-y1));
324 // concatenate modelview matrix
325 MTXConcat(mt, mv, mt);
326 GXLoadTexMtxImm(mt, GX_TEXMTX0, GX_MTX3x4);
327
328 //
329 // Set viewport and scissor for mirror
330 //
331 GXSetViewport(vp[0], vp[1], vp[2], vp[3], vp[4], vp[5]);
332 GXSetScissor((u16)minx, (u16)miny, width, height);
333
334 //
335 // Draw mirrored scene
336 //
337 DrawScene(GX_TRUE);
338
339 if (testing)
340 {
341 // Show only the computed reflection scene and bounding box
342 Mtx mv;
343
344 // Setup to draw bounding box
345 GXSetNumTexGens(0);
346 GXSetNumChans(1);
347 GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
348 GXClearVtxDesc();
349 GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
350 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL,
351 GX_TEXMAP_NULL, GX_COLOR0A0);
352 MTXIdentity(mv);
353 mv[0][3] = -(XCENTER/VpScale)+0.5f;
354 mv[1][1] = -1;
355 mv[1][3] = (YCENTER/VpScale)-1.5f;
356 mv[2][2] = -(YCENTER/VpScale)/tanf(FOVY/2.0f*PI/180.0f);
357 GXLoadPosMtxImm(mv, GX_PNMTX0);
358
359 // Drawing bounding box
360 GXBegin(GX_LINESTRIP, GX_VTXFMT0, 5);
361 GXPosition3f32( minx, miny, 1 );
362 GXPosition3f32( maxx, miny, 1 );
363 GXPosition3f32( maxx, maxy, 1 );
364 GXPosition3f32( minx, maxy, 1 );
365 GXPosition3f32( minx, miny, 1 );
366 GXEnd();
367
368 return;
369 }
370
371 // See comment further below regarding this draw sync
372 GXSetDrawSync(++count);
373
374 //
375 // Do as much work as possible before checking draw sync
376 // to make sure texture rendering is done.
377 //
378
379 // Init mirror texture object with newly computed size
380 GXInitTexObj( &mirr_obj,
381 MirrTexData,
382 width,
383 height,
384 GX_TF_I8,
385 GX_CLAMP,
386 GX_CLAMP,
387 GX_FALSE );
388
389 // Set copy source location
390 GXSetTexCopySrc((u16) minx, (u16) miny, width, height);
391 GXSetTexCopyDst((u16) width, (u16) height, GX_TF_I8, GX_FALSE);
392
393 // Wait for texture to be rendered before copying to memory
394 // One could also have used GXPixModeSync(), which simply
395 // flushes the rasterizer. Using draw sync allows other commands
396 // to be executed in place of the flush (although in this case
397 // there are very few).
398 while( count != GXReadDrawSync() ) {};
399
400 // Copy texture
401 GXCopyTex(MirrTexData, GX_TRUE);
402
403 //
404 // Draw scene normally
405 //
406
407 // Adjust viewport width and height
408 vp[2] *= VpScale;
409 vp[3] *= VpScale;
410 GXSetViewportv(vp);
411 GXSetScissor((u16)vp[0], (u16)vp[1], (u16)vp[2], (u16)vp[3]);
412
413 // Draw normal scene
414 DrawScene(GX_FALSE);
415
416 // Invalidate texture cache; could also use GXInvalidateTexRegion
417 GXInvalidateTexAll();
418
419 // Draw mirror
420 DrawMirror( );
421 }
422
423 /*---------------------------------------------------------------------------*/
GetMirrorMv(VecPtr trans,VecPtr rot,Mtx mv)424 static void GetMirrorMv( VecPtr trans, VecPtr rot, Mtx mv )
425 {
426 Mtx tm;
427 MTXRotDeg(mv, 'X', rot->x);
428 MTXRotDeg(tm, 'Y', rot->y); // ignore rot->z for now
429 MTXConcat(tm, mv, mv);
430 MTXTrans(tm, trans->x, trans->y, trans->z);
431 MTXConcat(tm, mv, mv);
432 }
433
434 /*---------------------------------------------------------------------------*/
DrawMirror(void)435 static void DrawMirror ( void )
436 {
437 s32 i;
438 Mtx mv;
439
440 GXClearVtxDesc();
441 GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
442
443 GXSetNumTexGens(1);
444 GXSetNumChans(1);
445 GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE);
446 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
447 // Set up projected texture coordinate generation
448 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_TEXMTX0 );
449
450 // Load mirror texture object
451 GXLoadTexObj(&mirr_obj, GX_TEXMAP0);
452
453 // Set up matrices
454 GetMirrorMv( &mirrTrans, &mirrRot, mv );
455 MTXConcat(v, mv, mv);
456 GXLoadPosMtxImm(mv, GX_PNMTX0);
457
458 GXBegin(GX_QUADS, GX_VTXFMT0, 4);
459 for (i = 0; i < 4; i ++) {
460 GXPosition3f32(MirrorVert[i*3], MirrorVert[i*3+1], MirrorVert[i*3+2]);
461 }
462 GXEnd();
463
464 //
465 // Draw back of mirror
466 //
467 GXSetVtxDesc(GX_VA_TEX0, GX_NONE);
468 GXSetNumTexGens(0);
469 GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
470 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
471
472 GXBegin(GX_QUADS, GX_VTXFMT0, 4);
473 for (i = 3; i >= 0; i--) {
474 GXPosition3f32(MirrorVert[i*3], MirrorVert[i*3+1], MirrorVert[i*3+2]);
475 }
476 GXEnd();
477 }
478
479 /*---------------------------------------------------------------------------*/
DrawScene(GXBool mirror)480 static void DrawScene ( GXBool mirror )
481 {
482 Mtx ry, rz, mmv, mv, t, refl;
483 Vec p = {0.0f, 0.0f, 0.0f}; // point on planar reflector
484 Vec n = {0.0f, 0.0f, -1.0f}; // normal of planar reflector
485
486 GXClearVtxDesc();
487 GXSetVtxDesc(GX_VA_POS, GX_INDEX16);
488 GXSetVtxDesc(GX_VA_NRM, GX_INDEX16);
489 GXSetVtxDesc(GX_VA_TEX0, GX_INDEX16);
490
491 GXSetNumTexGens(1);
492 GXSetNumChans(0);
493 GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
494 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
495 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
496
497 GXLoadTexObj(&face_obj, GX_TEXMAP0);
498
499 // Compute model matrix
500 MTXRotDeg(ry, 'Y', (float)rot);
501 MTXRotDeg(rz, 'Z', (float)rot);
502 MTXTrans(t, -80.0f, 150.0f, 0);
503 MTXConcat(rz, ry, mv);
504 MTXConcat(t, mv, mv);
505
506 // Calculate reflection modelview matrix if in mirror space
507 if (mirror)
508 {
509 GetMirrorMv( &mirrTrans, &mirrRot, mmv );
510
511 MTXMultVec(mmv, &p, &p);
512 MTXInverse(mmv, mmv);
513 MTXTranspose(mmv, mmv);
514 MTXMultVec(mmv, &n, &n);
515 VECNormalize(&n, &n);
516
517 MTXReflect(refl, &p, &n);
518 MTXConcat(refl, mv, mv);
519
520 GXSetCullMode( GX_CULL_FRONT ); // geometry is mirrored in mirror space
521
522 } else {
523
524 GXSetCullMode( GX_CULL_BACK ); // for normal space
525 }
526
527 MTXConcat(v, mv, mv);
528 GXLoadPosMtxImm(mv, GX_PNMTX0);
529 MTXInverse(mv, mv);
530 MTXTranspose(mv, mv);
531 GXLoadNrmMtxImm(mv, GX_PNMTX0);
532
533 // Draw the cube
534
535 GXBegin(GX_QUADS, GX_VTXFMT0, 4*6);
536
537 SendVertex(0, 0);
538 SendVertex(1, 1);
539 SendVertex(2, 2);
540 SendVertex(3, 3);
541
542 SendVertex(4, 0);
543 SendVertex(5, 1);
544 SendVertex(6, 2);
545 SendVertex(7, 3);
546
547 SendVertex(2, 0);
548 SendVertex(6, 1);
549 SendVertex(5, 2);
550 SendVertex(3, 3);
551
552 SendVertex(1, 0);
553 SendVertex(0, 1);
554 SendVertex(4, 2);
555 SendVertex(7, 3);
556
557 SendVertex(5, 0);
558 SendVertex(4, 1);
559 SendVertex(0, 2);
560 SendVertex(3, 3);
561
562 SendVertex(6, 0);
563 SendVertex(2, 1);
564 SendVertex(1, 2);
565 SendVertex(7, 3);
566
567 GXEnd();
568 }
569
570 /*---------------------------------------------------------------------------*/
SendVertex(u16 posIndex,u16 texCoordIndex)571 static void SendVertex ( u16 posIndex, u16 texCoordIndex )
572 {
573 GXPosition1x16(posIndex);
574 GXNormal1x16(posIndex);
575 GXTexCoord1x16(texCoordIndex);
576 }
577
578 /*---------------------------------------------------------------------------*
579 Name: AnimTick
580
581 Description: Animates the scene based on the joystick's state.
582
583 Arguments: none
584
585 Returns: none
586 *---------------------------------------------------------------------------*/
AnimTick(void)587 static void AnimTick ( void )
588 {
589 u16 buttons = DEMOPadGetButtonDown(0);
590
591 if (buttons & PAD_BUTTON_X)
592 {
593 CurrentControl ++;
594 if (CurrentControl > 2)
595 CurrentControl = 0;
596
597 switch(CurrentControl)
598 {
599 case 0:
600 OSReport("\n\n1:1 reflected texture scale\n\n");
601 VpScale = 1;
602 break;
603
604 case 1:
605 OSReport("\n\n1:2 reflected texture scale\n\n");
606 VpScale = 2;
607 break;
608
609 case 2:
610 OSReport("\n\n1:4 reflected texture scale\n\n");
611 VpScale = 4;
612 break;
613 }
614 }
615
616 if (buttons & PAD_BUTTON_A)
617 {
618 testing = !testing;
619 }
620
621 if (buttons & PAD_BUTTON_B)
622 {
623 DoRotation = !DoRotation;
624 }
625
626 if (DoRotation)
627 {
628 rot ++;
629 if (rot > 1439)
630 rot = 0;
631 }
632
633 if (DEMOPadGetStickX(0) > 0)
634 mirrRot.y += 2;
635 else if (DEMOPadGetStickX(0) < 0)
636 mirrRot.y -= 2;
637
638 if (DEMOPadGetStickY(0) > 0)
639 mirrRot.x += 2;
640 else if (DEMOPadGetStickY(0) < 0)
641 mirrRot.x -= 2;
642
643 if (DEMOPadGetSubStickX(0) > 0)
644 mirrTrans.x += 2;
645 else if (DEMOPadGetSubStickX(0) < 0)
646 mirrTrans.x -= 2;
647 Clamp(mirrTrans.x, -200, 200);
648
649
650 if (DEMOPadGetSubStickY(0) > 0)
651 mirrTrans.y += 2;
652 else if (DEMOPadGetSubStickY(0) < 0)
653 mirrTrans.y -= 2;
654 Clamp(mirrTrans.y, -200, 200);
655 }
656
657 /*---------------------------------------------------------------------------*
658 Name: PrintIntro
659
660 Description: Prints the directions on how to use this demo.
661
662 Arguments: none
663
664 Returns: none
665 *---------------------------------------------------------------------------*/
PrintIntro(void)666 static void PrintIntro( void )
667 {
668 OSReport("\n\n");
669 OSReport("******************************************************\n");
670 OSReport("tf-mirror: projection texture demo\n");
671 OSReport("******************************************************\n");
672 OSReport("to quit hit the start button\n");
673 OSReport("\n");
674 OSReport("Main stick : Rotate the mirror\n");
675 OSReport("Sub stick : Move the mirror\n");
676 OSReport("A Button : Watch projection viewport\n");
677 OSReport("B Button : Enable/Disable cube rotation\n");
678 OSReport("X Button : Change projection texture scale\n");
679 OSReport("******************************************************\n");
680 OSReport("\n\n");
681 }
682
683 /*===========================================================================*/
684