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