1 /*---------------------------------------------------------------------------*
2   Project:  Dolphin/Revolution gx demo
3   File:     G2D-testPhy.c (Test of 2D API by Paul Donnelly, Nov. 1999)
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 #include "G2D-test.h"
18 
19 /*---------------------------------------------------------------------------*
20    Defines
21  *---------------------------------------------------------------------------*/
22 #define SIMULATION_FREQUENCY  75.0F
23 #define FRAME_RATE            75.0F
24 
25 #define ANGULAR_DAMPING       0.94F
26 #define LINEAR_DAMPING        0.975F
27 #define ANGULAR_BRAKING       0.75F
28 #define LINEAR_BRAKING        0.85F
29 
30 // Angular acceleration in radians per timestep-squared
31 //#define ANGULAR_ACCELERATION  (590.0F / (SIMULATION_FREQUENCY * SIMULATION_FREQUENCY))
32 #define ANGULAR_ACCELERATION  (10.0F / (SIMULATION_FREQUENCY * SIMULATION_FREQUENCY))
33 
34 // Linear acceleration in world pixels per timestep-squared
35 #define LINEAR_ACCELERATION   (1500.0F /  (SIMULATION_FREQUENCY * SIMULATION_FREQUENCY))
36 
37 #define ONE_OVER_127          0.007874F  // max/min controller (x,y) = +/- 127
38 #define PI_TIMES_2            6.283185307179586476925286766559F
39 
40 #define MAX_ITNS1           4
41 #define COEF_RESTITUTION   -0.4F
42 #define VELOCITY_LOOKAHEAD  10.0F        // Ratio of velocity-per-timestep to camera lookahead point
43 
44 /*---------------------------------------------------------------------------*
45    Global Variables
46  *---------------------------------------------------------------------------*/
47 
48 G2DPosOri poCam;
49 G2DPosOri poShip;
50 
51 s32 nStartIdx;
52 u32 nMode = 0;
53 u8 *map;
54 s32 nMapWidth, nMapHeight;
55 f32 rWorldWidth, rWorldHeight;
56 
57 /*---------------------------------------------------------------------------*
58    Static Global Variables
59  *---------------------------------------------------------------------------*/
60 
61 static f32 rVelX =  0.0F, rVelY = 0.0F;
62 static f32 rAngV = 0.0F;
63 f32 rAng = 0.0F;
64 static f32 rRelCamX, rRelCamY, rCamA;
65 static f32 rCVelX, rCVelY, rCVelA;
66 static f32 rCIntX, rCIntY, rCIntA;
67 static f32 rCamI;
68 
69 u16 nButtons;
70 u16 nOldButtons = 0;
71 s8 stickX;
72 s8 stickY;
73 
74 static s16 nViewportTlcX = 0;
75 static s16 nViewportTlcY = 0;
76 static s16 nViewportVelX = 1;
77 static s16 nViewportVelY = 1;
78 
79 #if _OUTPUT
80     #define MAX_RECORDS 8000
81 
82     typedef struct
83     {
84         f32 rPosX;
85         f32 rPosY;
86         f32 rVelX;
87         f32 rVelY;
88         f32 rAng;
89         f32 rAngV;
90     }
91     Record;
92 
93     Record aRecord[MAX_RECORDS];
94 
95     FILE *fpOut;
96 #endif
97 
98 static void Options ( u16 nButtons );
99 static void UpdatePosition( f32 rRadius );
100 static void HandleCollisions( f32 *pRemainingTime, f32 rRadius );
101 static __inline s32 Collide(s32 nMx, s32 nMy, f32 rRad, f32 *pTime, f32 *pNx, f32 *pNy );
102 static void MoveShip( void );
103 
104 
105 /*---------------------------------------------------------------------------*
106     Name:           AnimTick
107 
108     Description:    Updates the objects in the world by one timestep
109 
110     Arguments:      none
111 
112     Returns:        none
113  *---------------------------------------------------------------------------*/
AnimateViewport()114 static void AnimateViewport()
115 {
116     G2DSetViewport((u16)nViewportTlcX, (u16)nViewportTlcY,
117                    MY_SCREEN_WIDTH*3/4, MY_SCREEN_HEIGHT*3/4);
118 
119     nViewportTlcX += nViewportVelX;
120     nViewportTlcY += nViewportVelY;
121 
122     if ((nViewportTlcX == 0) || (nViewportTlcX == MY_SCREEN_WIDTH*1/4))
123     {
124         nViewportVelX *= -1;
125     }
126 
127     if ((nViewportTlcY == 0) || (nViewportTlcY == MY_SCREEN_HEIGHT*1/4))
128     {
129         nViewportVelY *= -1;
130     }
131 }
132 
133 
PhysicsInit()134 static void PhysicsInit()
135 {
136     /*  To handle different frame rates but achieve the same motion speeds
137      *  we scale the acceleration and damping according to the following
138      *  equations:
139      *                                   FrameRatio
140      *  NewAcc = Acc * Damping * (Damping           -  1)
141      *           ----------------------------------------
142      *                        (Damping - 1)
143      *
144      *
145      *                      FrameRatio
146      *  NewDamping = Damping
147      *
148      */
149 
150     f32 rRatio = SIMULATION_FREQUENCY * 0.01667F;
151 
152     //rLinearAcceleration =
153 }
154 
155 
156 /*---------------------------------------------------------------------------*
157     Name:           CameraInit
158 
159     Description:    Initialize the projection matrix and load into hardware.
160 
161     Arguments:      none
162     Returns:        none
163  *---------------------------------------------------------------------------*/
CameraInit(void)164 static void CameraInit      ( void )
165 {
166     poCam.rPosX = poShip.rPosX + (VELOCITY_LOOKAHEAD * rVelX);
167     poCam.rPosY = poShip.rPosY + (VELOCITY_LOOKAHEAD * rVelY);
168     rRelCamX = 0.0F;
169     rRelCamY = 0.0F;
170     rCamA = rAng;
171     rCVelX = 0.0F;
172     rCVelY = 0.0F;
173     rCVelA = 0.0F;
174     rCIntX = 0.0F;
175     rCIntY = 0.0F;
176     rCIntA = 0.0F;
177     rCamI = 0.9F;
178 }
179 
180 /*---------------------------------------------------------------------------*
181     Name:           CameraUpdate
182 
183     Description:    Updates the camera object based on the joystick's state.
184 
185     Arguments:      none
186 
187     Returns:        none
188  *---------------------------------------------------------------------------*/
CameraUpdate()189 static void CameraUpdate()
190 {
191     f32 rCamDesiredX, rCamDesiredY, rCamDesiredA;
192 
193     // Camera motion implemented with a PID controller
194 #if _OUTPUT
195     fprintf(fpOut, "Pos(%7.4f,%7.4f) Vel(%7.4f,%7.4f) Ang:%7.2f AngV:%7.3f Cam(%7.4f,%7.4f)\n",
196         poShip.rPosX, poShip.rPosY, rVelX, rVelY, rAng, rAngV, poCam.rPosX, poCam.rPosY);
197 #endif
198 
199     rCamDesiredX = VELOCITY_LOOKAHEAD * rVelX;
200     rCamDesiredY = VELOCITY_LOOKAHEAD * rVelY;
201     rCamDesiredA = rAng;
202 
203     rCIntX = (rCamI * rCIntX) + ((1.0F - rCamI) * (rCamDesiredX - rRelCamX));
204     rCIntY = (rCamI * rCIntY) + ((1.0F - rCamI) * (rCamDesiredY - rRelCamY));
205     rCIntA = (rCamI * rCIntA) + ((1.0F - rCamI) * (rCamDesiredA - rCamA));
206 
207     rCVelX += ((rCamDesiredX - rRelCamX) * 0.04F) + (rCIntX * 0.05F);
208     rCVelY += ((rCamDesiredY - rRelCamY) * 0.04F) + (rCIntY * 0.05F);
209     rCVelA += ((rCamDesiredA - rCamA)    * 0.05F) + (rCIntA * 0.1F);
210 
211     rCVelX *= 0.4F;
212     rCVelY *= 0.4F;
213     rCVelA *= 0.4F;
214 
215     rRelCamX += rCVelX;
216     rRelCamY += rCVelY;
217     rCamA += rCVelA;
218 
219     poCam.rOriX = (f32)  sin(rCamA);
220     poCam.rOriY = (f32) -cos(rCamA);
221 
222     poCam.rPosX = poShip.rPosX + rRelCamX;
223     poCam.rPosY = poShip.rPosY + rRelCamY;
224 
225     if (poCam.rPosX >= rWorldWidth)  { poCam.rPosX -= rWorldWidth; }
226     else if (poCam.rPosX < 0)        { poCam.rPosX += rWorldWidth; }
227     if (poCam.rPosY >= rWorldHeight) { poCam.rPosY -= rWorldHeight; }
228     else if (poCam.rPosY < 0)        { poCam.rPosY += rWorldHeight; }
229 
230     G2DSetCamera( &poCam );
231 }
232 
233 static f32 rPx, rPy;
234 static f32 rVx, rVy;
235 
Collide(s32 nMx,s32 nMy,f32 rRad,f32 * pTime,f32 * pNx,f32 * pNy)236 static __inline s32 Collide(s32 nMx, s32 nMy, f32 rRad, f32 *pTime, f32 *pNx, f32 *pNy )
237 /*  Test for collision, and if colliding, do the collision response (bounce)
238  *  return 1 for colliding, 0 for not colliding
239  */
240 {
241     s32 nTile;      // Index of tile (shifted left by 2 to index tileDesc array)
242     s32 nCollType;  // Tile collision type
243     s32 nRtn;       // Tile rotation (anticlockwise in 90deg steps)
244 
245     nTile = map[(nMx & (nMapWidth-1)) + ((nMy & (nMapHeight-1)) * nMapWidth)];
246     nCollType = lyrBack.tileDesc[nTile].aUser[1];
247 
248     if (nCollType == 0)
249     {
250         // Non-collision tiles
251 
252         return 0;
253     }
254 
255     if (nCollType == 3)
256     {
257         // Collision with a mid-section
258         return 0;
259     }
260 
261     // We will have actual collisions to process from here
262     nRtn = lyrBack.tileDesc[nTile].aUser[0];
263 
264     if (nCollType == 2)
265     {
266         // Collision with a border tile
267 
268         f32 rX, rY, rRatio;
269 
270         switch(nRtn)
271         {
272             case 0:
273             {
274                 if (rVy <= 0.0F)
275                 {
276                     return 0;
277                 }
278 
279                 // line Y = nMy + 0.1
280                 rY = (f32)nMy + 0.1F - rRad;
281                 rRatio = (rY - rPy) / rVy;
282                 break;
283             }
284             case 1:
285             {
286                 if (rVx <= 0.0F)
287                 {
288                     return 0;
289                 }
290 
291                 // line X = nMx + 0.1
292                 rX = (f32)nMx + 0.1F - rRad;
293                 rRatio = (rX - rPx) / rVx;
294                 break;
295             }
296             case 2:
297             {
298                 if (rVy >= 0.0F)
299                 {
300                     return 0;
301                 }
302 
303                 //line Y = nMy + 0.9
304                 rY = (f32)nMy + 0.9F + rRad;
305                 rRatio = (rY - rPy) / rVy;
306                 break;
307             }
308             case 3:
309             {
310                 if (rVx >= 0.0F)
311                 {
312                     return 0;
313                 }
314 
315                 // line X = nMx + 0.9
316                 rX = (f32)nMx + 0.9F + rRad;
317                 rRatio = (rX - rPx) / rVx;
318                 break;
319             }
320         }
321 
322         if ((rRatio < *pTime) && (rRatio > 0.0F))
323         {
324             *pTime = rRatio;
325 
326             if (nRtn & 1)
327             {
328                 *pNx = 1.0F;
329                 *pNy = 0.0F;
330             }
331             else
332             {
333                 *pNx = 0.0F;
334                 *pNy = 1.0F;
335             }
336 
337             return 1;
338         }
339 
340         return 0;
341     }
342 
343     //  The remaining tiles are conic sections
344     {
345         f32 rCx, rCy;
346         f32 rDx, rDy;
347         f32 rA, rB, rC, rD;
348         f32 rT1;
349 
350         // Find centre of conic section (rCx, rCy)
351 
352         rCx = (f32)nMx;
353         rCy = (f32)nMy;
354 
355         if (nRtn < 2)
356         {
357             rCx += 1.0F;
358         }
359 
360         if (((nRtn+1)&3) < 2)
361         {
362             rCy += 1.0F;
363         }
364 
365         // Calculate roots of quadratic
366 
367         rDx = rCx - rPx;
368         rDy = rCy - rPy;
369 
370         rA = (rVx * rVx) + (rVy * rVy);
371         rB = (rRad + 0.9F) * (rRad + 0.9F) * rA;
372         rC = (rDy * rVx) - (rDx * rVy);
373         rD = rB - (rC * rC);
374         if (rD < 0.0F)
375         {
376             return 0;
377         }
378         rD = (f32)sqrt(rD);
379 
380         rA = 1.0F / rA;
381         rB = (rDx * rVx) + (rDy * rVy);
382         rT1 = rA * (rB - rD);
383         //rT2 = rA * (rB + rD);
384 
385         if (rT1 < 0.0F)
386         {
387             // We must be starting from inside the circle
388             return 0;
389         }
390 
391         if (rT1 < *pTime)
392         {
393             *pTime = rT1;
394 
395             // Now to calculate the normal at the point of collision
396             *pNx = rPx + (rT1 * rVx) - rCx;
397             *pNy = rPy + (rT1 * rVy) - rCy;
398 
399             // The normal must be normalized
400             rA = 1.0F / (f32)sqrt((*pNx * *pNx) + (*pNy * *pNy));
401             *pNx *= rA;
402             *pNy *= rA;
403 
404             return 1;
405         }
406 
407         return 0;
408     }
409 }
410 
411 
HandleCollisions(f32 * pRemainingTime,f32 rRadius)412 static void HandleCollisions( f32 *pRemainingTime, f32 rRadius )
413 {
414     s32 nIncX, nIncY;   // Offsets to possibly overlapping map coordinates
415     f32 rNewPosX, rNewPosY;
416     s32 nMx, nMy;
417     f32 rFx, rFy;
418     f32 rTime = 1.0F;
419     f32 rNx, rNy;   // Collision Normal
420 
421     rNewPosX = rPx + (*pRemainingTime * rVx);
422     rNewPosY = rPy + (*pRemainingTime * rVy);
423 
424     nMx = (s32)rNewPosX;
425     nMy = (s32)rNewPosY;
426 
427     rFx = rNewPosX - nMx;
428     rFy = rNewPosY - nMy;
429 
430     nIncX = -1;
431     nIncY = -1;
432 
433     if (rFx > 0.5F)
434     {
435         rFx = 1.0F - rFx;
436         nIncX = 1;
437     }
438 
439     if (rFy > 0.5F)
440     {
441         rFy = 1.0F - rFy;
442         nIncY = 1;
443     }
444 
445     /*  Potential collision with centre tile (after step)
446      */
447     Collide( nMx, nMy, rRadius, &rTime, &rNx, &rNy );
448 
449     if (rFx < rRadius)
450     {
451         // Potential collision with horizontally adjacent tile
452         Collide( nMx + nIncX, nMy, rRadius, &rTime, &rNx, &rNy );
453 
454         if (rFy < rRadius)
455         {
456             /*  Potential collisions with vertically adjacent and
457              *  diagonally adjacent tiles
458              */
459 
460             // Potential collision with vertically adjacent tile
461             Collide( nMx, nMy + nIncY, rRadius, &rTime, &rNx, &rNy );
462 
463             // Potential collision with diagonally adjacent tile
464             Collide( nMx + nIncX, nMy + nIncY, rRadius, &rTime, &rNx, &rNy );
465         }
466     }
467     else if (rFy < rRadius)
468     {
469         // Potential collision with vertically adjacent tile
470         Collide( nMx, nMy + nIncY, rRadius, &rTime, &rNx, &rNy );
471     }
472 
473     if (rTime < *pRemainingTime)
474     {
475         f32 rN, rT;
476 
477         // We bumped into something within the timestep
478         rTime -= 0.01F; // avoids numerical accuracy problems
479         rPx += (rTime * rVx);
480         rPy += (rTime * rVy);
481         *pRemainingTime -= rTime;
482 
483         // Reflect normal component of velocity
484         // Now resolve velocity in normal and tangential directions
485         rN = (rVx * rNx) + (rVy * rNy); // Normal
486         rT = (rVx * rNy) - (rVy * rNx); // Tangent
487 
488         // Scale normal velocity by coefficient of restitution
489         rN *= COEF_RESTITUTION;
490 
491         // Reconstitute new velocity
492         rVx = (rN * rNx) + (rT * rNy);
493         rVy = (rN * rNy) - (rT * rNx);
494 
495         return;
496     }
497 
498     // We didn't bump into anything
499     rPx += (*pRemainingTime * rVx);
500     rPy += (*pRemainingTime * rVy);
501     *pRemainingTime = 0.0F;
502 }
503 
504 
505 /*---------------------------------------------------------------------------*
506     Name:           UpdatePosition
507 
508     Description:    Update position of ship taking collisions into consideration
509 
510     Arguments:      rRadius    Radius of collision circle
511 
512     Returns:        none
513  *---------------------------------------------------------------------------*/
UpdatePosition(f32 rRadius)514 static void UpdatePosition( f32 rRadius )
515 {
516     f32 rStepRemaining = 1.0F;
517     s32 nItns1;
518 
519     rPx = poShip.rPosX / lyrBack.nTileWidth;    // The collision routines won't really look
520     rPy = poShip.rPosY / lyrBack.nTileHeight;   // correct unless TILE_WIDTH_LYR1 == TILE_HEIGHT_LYR1
521     rVx = rVelX / lyrBack.nTileWidth;
522     rVy = rVelY / lyrBack.nTileHeight;
523 
524     for(nItns1=0; nItns1 < MAX_ITNS1; nItns1++)
525     {
526         // Count collisions at end of step
527         HandleCollisions( &rStepRemaining, rRadius );
528 
529         if (rStepRemaining < 0.01)
530         {
531             break;
532         }
533     }
534 
535     poShip.rPosX = rPx * lyrBack.nTileWidth;    // The collision routines won't really look
536     poShip.rPosY = rPy * lyrBack.nTileHeight;   // correct unless TILE_WIDTH_LYR1 == TILE_HEIGHT_LYR1
537     rVelX = rVx * lyrBack.nTileWidth;
538     rVelY = rVy * lyrBack.nTileHeight;
539 }
540 
541 
AnimInit(void)542 void AnimInit( void )
543 {
544     CameraInit();
545 
546     poShip.rOriX = (f32) sin(rAng);
547     poShip.rOriY = (f32) -cos(rAng);
548 
549     map = lyrBack.map;
550     rWorldWidth = (f32)(lyrBack.nTileWidth << lyrBack.nHS);
551     rWorldHeight = (f32)(lyrBack.nTileHeight << lyrBack.nVS);
552     nMapWidth = 1<<lyrBack.nHS;
553     nMapHeight = 1<<lyrBack.nVS;
554 
555 #if _OUTPUT
556     fpOut = fopen("out.txt", "wb");
557 #endif
558 }
559 
560 
561 /*---------------------------------------------------------------------------*
562     Name:           MoveShip
563 
564     Description:    Updates the ship by one timestep
565 
566     Arguments:      none
567 
568     Returns:        none
569  *---------------------------------------------------------------------------*/
MoveShip(void)570 static void MoveShip( void )
571 {
572     static u32 nGameTime = 0;
573 
574     if (nButtons & PAD_TRIGGER_L)
575     {
576         rAngV = rAngV - ANGULAR_ACCELERATION;
577     }
578     if (nButtons & PAD_TRIGGER_R)
579     {
580         rAngV = rAngV + ANGULAR_ACCELERATION;
581     }
582 
583     //rAngV = rAngV + stickX * 0.01575F;
584 
585 /*  if (nButtons & PAD_BUTTON_A)
586     {
587         rVelX -= (poShip.rOriX * 0.04F);
588         rVelY -= (poShip.rOriY * 0.04F);
589     }
590 */
591     rVelX += (poShip.rOriX * LINEAR_ACCELERATION * ONE_OVER_127 * stickY);
592     rVelY += (poShip.rOriY * LINEAR_ACCELERATION * ONE_OVER_127 * stickY);
593 
594     if (nButtons & PAD_BUTTON_B)
595     {
596         rVelX *= LINEAR_BRAKING;
597         rVelY *= LINEAR_BRAKING;
598         rAngV *= ANGULAR_BRAKING;
599     }
600     else
601     {
602         rVelX *= LINEAR_DAMPING;
603         rVelY *= LINEAR_DAMPING;
604         rAngV *= ANGULAR_DAMPING;
605     }
606 
607     // Orientation can be updated trivially
608     rAng += rAngV;
609     if (rAng > PI_TIMES_2) { rAng -= PI_TIMES_2; rCamA -= PI_TIMES_2; }
610     if (rAng < 0) { rAng += PI_TIMES_2; rCamA += PI_TIMES_2; }
611 
612     poShip.rOriX = (f32)  sin(rAng);
613     poShip.rOriY = (f32) -cos(rAng);
614 
615     // Position must be updated carefully checking for collisions
616     UpdatePosition( 0.4F );
617 
618     if (poShip.rPosX >= rWorldWidth)  { poShip.rPosX -= rWorldWidth; }
619     else if (poShip.rPosX < 0)        { poShip.rPosX += rWorldWidth; }
620     if (poShip.rPosY >= rWorldHeight) { poShip.rPosY -= rWorldHeight; }
621     else if (poShip.rPosY < 0)        { poShip.rPosY += rWorldHeight; }
622 #if _OUTPUT
623     aRecord[nGameTime].rPosX = poShip.rPosX;
624     aRecord[nGameTime].rPosY = poShip.rPosY;
625     aRecord[nGameTime].rVelX = rVelX;
626     aRecord[nGameTime].rVelY = rVelY;
627     aRecord[nGameTime].rAng  = rAng;
628     aRecord[nGameTime].rAngV = rAngV;
629     if (nGameTime >= MAX_RECORDS)
630     {
631         fclose(fpOut);
632         fpOut = fopen("out.txt", "wb");
633         nGameTime=0;
634     }
635 #endif
636     nGameTime++;
637 }
638 
639 
640 /*---------------------------------------------------------------------------*
641     Name:           AnimTick
642 
643     Description:    Updates the objects in the world by one timestep
644 
645     Arguments:      none
646 
647     Returns:        none
648  *---------------------------------------------------------------------------*/
AnimTick(void)649 void AnimTick( void )
650 {
651     /*  Read controller
652      */
653     nButtons = DEMOPadGetButton(0);
654     stickX = DEMOPadGetStickX(0);
655     stickY = DEMOPadGetStickY(0);
656 
657     if (nMode == 2)
658     {
659         MapEditor( &lyrBack );
660     }
661     else
662     {
663         MoveShip();
664     }
665 
666     if ((nButtons & ~nOldButtons) & PAD_BUTTON_Y)
667     {
668         if (nMode == 2)
669         {
670             /* Save edits to map */
671             SaveMap( &lyrBack );
672         }
673 
674 #ifdef _EDITOR
675         nMode = (nMode+1)%3;
676 
677         if (nMode == 2)
678         {
679             rAng = 0.0F;
680             poShip.rOriX = (f32)  sin(rAng);
681             poShip.rOriY = (f32) -cos(rAng);
682             aEditStamp[0] = '.'-' ';
683         }
684 #else
685         nMode = (nMode+1)%2;
686 #endif // ifdef _EDITOR
687 
688         if (nMode != 1)
689         {
690             G2DSetViewport( 0, 0, MY_SCREEN_WIDTH, MY_SCREEN_HEIGHT );
691         }
692     }
693 
694     CameraUpdate();
695     if (nMode == 1)
696     {
697         AnimateViewport();
698     }
699 
700     nOldButtons = nButtons;
701 }
702