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