1 /*---------------------------------------------------------------------------*
2 Project: Dolphin/Revolution gx demo
3 File: geo-particle.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 #include <stdio.h>
17
18 /*---------------------------------------------------------------------------*
19 Defines
20 *---------------------------------------------------------------------------*/
21
22 #define HEIGHT 20
23 #define WIDTH 12
24
25 #define WIDTH_SAMPLES 32
26 #define HEIGHT_SAMPLES 32
27 #define TOTAL_SAMPLES (WIDTH_SAMPLES * HEIGHT_SAMPLES)
28
29 #define MAX_PARTICLES 1600
30 #define MAX_EMISSION_RATE 50
31 #define EMMISION_RATE 5.0F
32
33 #define LIFESPAN_MEAN 125.0F
34 #define LIFESPAN_STDDEV 25.0F
35 #define SIZE_MEAN 200.0F
36 #define SIZE_MAX 640.0F
37 #define SIZE_STDDEV 40.0F
38
39 #define PARTICLE_SPEED 4.0F
40 #define GRAVITY -0.03F
41 #define EVENT_TIME 160
42
43 #define ACTIVE_LIST MAX_PARTICLES
44 #define FREE_LIST (MAX_PARTICLES + 1)
45
46 #define LINE_SEGMENT 0x0001
47 #define PARTICLE_HAS_SIZE 0x0002
48 #define ANIMATE_SIZE 0x0004
49 #define INVERSE_ANIMATE 0x0008
50 #define TEXTURE_PARTICLE 0x0010
51 #define TEXWIDE_PARTICLE 0x0020
52
53 /*---------------------------------------------------------------------------*
54 Local Typedefs
55 *---------------------------------------------------------------------------*/
56
57 // Emitter State
58 typedef struct
59 {
60 Mtx mModel; // modeling matrix (orientation)
61 Vec vPos; // position of emitter
62 Vec vLinVel; // linear velocity per tick
63 Vec vAngVel; // angular velocity per tick
64 f32 rEmitted; // counts particles emitted
65 f32 rEmissionRate; // number of particles emitted per tick
66 f32 rLifespanMean; // average lifespan of emitted particles
67 f32 rLifespanStdDev; // standard deviation of lifespan of emitted particles
68 f32 rSizeMean; // average size of emitted particles
69 f32 rSizeStdDev; // standard deviation of size of emitted particles
70 u32 nType; // type of particles emitted
71 GXTexOffset offset; // texture offset for texture particles
72 }
73 Emitter;
74
75 // Particle State
76 typedef struct
77 {
78 u32 nNext; // singularly linked list for sequential access + add/delete
79 Vec vPos; // position
80 Vec vVel; // velocity per tick
81 s32 nLifespan; // number of ticks of life left
82 u32 nType; // defines point/line and Lifespan->{size, color, texture} mappings
83 s32 nSize; // Size of Particle (only when PARTICLE_HAS_SIZE)
84 f32 tex_s, tex_t; // Texture coordinates
85 }
86 Particle;
87
88 typedef struct
89 {
90 Vec vPos;
91 Vec vUp;
92 VecPtr vpTarget;
93 }
94 Camera;
95
96 /*---------------------------------------------------------------------------*
97 Forward references
98 *---------------------------------------------------------------------------*/
99
100 void main ( void );
101
102 static void CameraInit ( void );
103 static void CameraUpdate (s8 stickX, s8 stickY);
104 static void DrawInit ( void );
105 static void DrawTick ( void );
106
107 static void AnimTick ( void );
108
109 static void PrintIntro ( void );
110
111 static void SetupTransforms ( void );
112 static void DrawParticles ( void );
113 static void DrawEmitter ( void );
114
115 static double BoxMuller ( void );
116 static void TextureParticlesInit( void );
117 static void ParticleInit ( void );
118 static void ParticleEmit ( Particle *prt, Emitter *em );
119 static void EmitterEmit ( Emitter *em);
120 static void ParticleUpdate ( void );
121 static void EmitterInit ( Emitter *em );
122 static void EmitterUpdate ( Emitter *em, s8 rotX, s8 rotY );
123
124 static void SendEmitterVertex ( u8 posIndex, u8 normalIndex,
125 u8 colorIndex, u8 texCoordIndex );
126 static void SendParticlePoint ( Vec *vPoint, u8 colorIndex );
127 static void SendParticleLine ( Vec *vPoint1, Vec *vDelta, u8 colorIndex );
128
129 static u32 rndi( void );
130 static void srnd( u32 x );
131 static double rndf( void );
132
133 /*---------------------------------------------------------------------------*
134 Global variables
135 *---------------------------------------------------------------------------*/
136
137 #define ULONG_MAX 4294967295
138 static u32 seed = 1; // Arbitrary
139
140 Emitter emMain; // Particle emitter
141 Camera cam;
142
143 Particle ParticleData[MAX_PARTICLES + 2];
144 u32 nNumActiveParticles;
145
146 u8 clrRGBA[4]; // color of particle
147
148 Mtx v;
149
150 TPLPalettePtr tpl = 0;
151 float FloatVert[] ATTRIBUTE_ALIGN(32) =
152 {
153 -WIDTH, HEIGHT, -WIDTH,
154 -WIDTH, HEIGHT, WIDTH,
155 -WIDTH, -HEIGHT, WIDTH,
156 -WIDTH, -HEIGHT, -WIDTH,
157 WIDTH, HEIGHT, -WIDTH,
158 WIDTH, -HEIGHT, -WIDTH,
159 WIDTH, -HEIGHT, WIDTH,
160 WIDTH, HEIGHT, WIDTH
161 };
162
163 u8 ColorRGBA8[] ATTRIBUTE_ALIGN(32) =
164 {
165 144, 48, 0, 0,
166 160, 64, 0, 16,
167 176, 80, 0, 32,
168 192, 96, 0, 48,
169 208, 112, 0, 64,
170 224, 128, 0, 80,
171 240, 144, 16, 96,
172 255, 160, 32, 112,
173 255, 176, 48, 128,
174 255, 192, 64, 144,
175 255, 208, 80, 160,
176 255, 224, 96, 176,
177 255, 240, 128, 192,
178 255, 255, 160, 208,
179 255, 255, 192, 224,
180 255, 255, 224, 240,
181 255, 255, 255, 255,
182 240, 255, 255, 255,
183 224, 255, 255, 255,
184 208, 255, 255, 255,
185 192, 240, 255, 255,
186 176, 224, 255, 255,
187 160, 208, 255, 255,
188 144, 192, 255, 255,
189 }; //GX_RGBA8
190
191 u8 EmitterColorRGBA8[] ATTRIBUTE_ALIGN(32) =
192 {
193 0, 0, 0, 255,
194 72, 72, 255, 255,
195 140, 60, 140, 255,
196 80, 128, 0, 255,
197 0, 140, 160, 255,
198 255, 255, 255, 255,
199 }; //GX_RGBA8
200
201 f32 FloatTex[] ATTRIBUTE_ALIGN(32) =
202 { 0.0F, 0.0F,
203 1.0F, 0.0F,
204 1.0F, 1.0F,
205 0.0F, 1.0F,
206 0.5F, 0.5F
207 };
208
209 f32 FloatNorm[] ATTRIBUTE_ALIGN(32) =
210 {
211 -1.0F, 0.0F, 0.0F,
212 1.0F, 0.0F, 0.0F,
213 0.0F, -1.0F, 0.0F,
214 0.0F, 1.0F, 0.0F,
215 0.0F, 0.0F, -1.0F,
216 0.0F, 0.0F, 1.0F
217 };
218
219 u8 MyPointTexture[4*8] ATTRIBUTE_ALIGN(32) =
220 {
221 0xaf, 0x0f, 0xf0, 0xf0,
222 0xff, 0x0f, 0xf0, 0xff,
223 0x00, 0x0f, 0xf0, 0x00,
224 0xff, 0xff, 0xff, 0xff,
225 0xff, 0xff, 0xff, 0xff,
226 0x00, 0x0f, 0xf0, 0x00,
227 0xff, 0x0f, 0xf0, 0xff,
228 0x0f, 0x0f, 0xf0, 0xf0
229 }; //GX_TF_I4
230
231
232 u32 CurrentControl = 0;
233 u32 TypeControl = 0;
234 u32 SizeAnimationControl = 0;
235 u32 NormalControl = 0;
236 u32 TexCoordControl = 0;
237
238 u8 PositionShift = 0;
239 u8 NormalShift = 0;
240 u8 TexCoordShift = 0;
241 s32 FrameNumber;
242
243 s16 GridOrder[TOTAL_SAMPLES];
244
245 /*---------------------------------------------------------------------------*
246 Application main loop
247 *---------------------------------------------------------------------------*/
main(void)248 void main ( void )
249 {
250 DEMOInit(NULL);
251
252 ParticleInit();
253 EmitterInit( &emMain );
254 DrawInit(); // Define my vertex formats and set array pointers.
255 PrintIntro();
256
257 DEMOPadRead(); // Read the joystick for this frame
258
259 // While the quit button is not pressed
260 while(!(DEMOPadGetButton(0) & PAD_BUTTON_MENU))
261 {
262 DEMOPadRead(); // Read the joystick for this frame
263
264 AnimTick(); // Do animation based on input
265 DEMOBeforeRender();
266
267 DrawTick(); // Draw the model.
268
269 DEMODoneRender();
270 }
271
272 OSHalt("End of test");
273 }
274
275 /*---------------------------------------------------------------------------*
276 Functions
277 *---------------------------------------------------------------------------*/
278
279 /*---------------------------------------------------------------------------*
280 Name: TextureParticlesInit
281
282 Description: Initialize parameters for texture particles
283
284 Arguments: none
285
286 Returns: none
287 *---------------------------------------------------------------------------*/
TextureParticlesInit()288 static void TextureParticlesInit()
289 {
290 u32 nI, nJ, nK;
291 s16 nT;
292
293 nJ=1;
294 for(nI=0; nI<TOTAL_SAMPLES; nI++)
295 {
296 //GridOrder[nI] = (s16)nI;
297 nJ = ((nJ*17)+19) % TOTAL_SAMPLES;
298 GridOrder[nI] = (s16)nJ;
299 }
300
301 // Make a number of shuffles
302 for(nI=0; nI<511; nI++)
303 {
304 nJ = rndi() % TOTAL_SAMPLES;
305 nK = rndi() % TOTAL_SAMPLES;
306
307 nT = GridOrder[nJ];
308 GridOrder[nJ] = GridOrder[nK];
309 GridOrder[nK] = nT;
310 }
311 }
312
313
314 #define LINE_SEGMENT 0x0001
315 #define PARTICLE_HAS_SIZE 0x0002
316 #define ANIMATE_SIZE 0x0004
317 #define INVERSE_ANIMATE 0x0008
318 /*---------------------------------------------------------------------------*
319 Name: CameraInit
320
321 Description: Initialize the projection matrix and load into hardware.
322
323 Arguments: none
324 Returns: none
325 *---------------------------------------------------------------------------*/
CameraInit(void)326 static void CameraInit ( void )
327 {
328 Mtx44 p;
329 Vec camPt = {0.0F, 0.0F, 650.0F};
330 Vec up = {0.0F, 1.0F, 0.0F};
331
332 MTXFrustum(p, 240, -240, -320, 320, 500, 2000);
333
334 GXSetProjection(p, GX_PERSPECTIVE);
335
336 cam.vPos = camPt;
337 cam.vUp = up;
338 cam.vpTarget = &emMain.vPos;
339
340 MTXIdentity(v);
341 }
342
343 /*---------------------------------------------------------------------------*
344 Name: CameraUpdate
345
346 Description: Updates the camera object based on the joystick's state.
347
348 Arguments: s8 stickX, stickY joystick direction (-127..+127, -127..+127)
349
350 Returns: none
351 *---------------------------------------------------------------------------*/
CameraUpdate(s8 stickX,s8 stickY)352 static void CameraUpdate(s8 stickX, s8 stickY)
353 {
354 Vec vVertical;
355 Vec vHorizontal;
356 Mtx m;
357
358 vVertical.x = v[0][1];
359 vVertical.y = v[1][1];
360 vVertical.z = v[2][1];
361
362 vHorizontal.x = v[0][0];
363 vHorizontal.y = v[1][0];
364 vHorizontal.z = v[2][0];
365
366 MTXRotAxisDeg(m, &vHorizontal, (float)stickY * 0.01F);
367 MTXMultVec(m, &cam.vPos, &cam.vPos);
368 MTXRotAxisDeg(m, &vVertical, (float)stickX * 0.01F);
369 MTXMultVec(m, &cam.vPos, &cam.vPos);
370 MTXLookAt(v, &cam.vPos, &vVertical, cam.vpTarget);
371 }
372
373 /*---------------------------------------------------------------------------*
374 Name: DrawInit
375
376 Description: Calls the correct initialization function for the current
377 model.
378
379 Arguments: none
380
381 Returns: none
382 *---------------------------------------------------------------------------*/
DrawInit(void)383 static void DrawInit( void )
384 {
385
386 GXTexObj to;
387
388 TPLGetPalette(&tpl, "gxTests/geo-00.tpl");
389
390 TPLGetGXTexObjFromPalette(tpl, &to, 0);
391
392 GXLoadTexObj(&to, GX_TEXMAP0);
393
394 // init my point texture
395 GXInitTexObj(
396 &to,
397 MyPointTexture,
398 8, 8, // wd, ht
399 GX_TF_I4,
400 GX_CLAMP, GX_CLAMP,
401 GX_FALSE);
402
403 GXLoadTexObj(&to, GX_TEXMAP1);
404
405 CameraInit(); // Initialize the camera.
406
407 GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA,
408 GX_LO_CLEAR);
409 GXSetZMode(FALSE, GX_ALWAYS, FALSE);
410 }
411
412 /*---------------------------------------------------------------------------*
413 Name: Options
414
415 Description: Selects test options
416
417 Arguments: u16 buttons button status
418
419 Returns: none
420 *---------------------------------------------------------------------------*/
Options(u16 buttons)421 static void Options ( u16 buttons )
422 {
423 if (buttons & PAD_BUTTON_X)
424 {
425 CurrentControl = (CurrentControl + 1) % 3;
426
427 switch(CurrentControl)
428 {
429 case 0:
430 OSReport("\nParticle Type Control\n");
431 break;
432 case 1:
433 OSReport("\nPoint Size Animation Control\n");
434 break;
435 }
436 }
437
438 if (CurrentControl == 0)
439 {
440 if(buttons & PAD_BUTTON_B)
441 {
442 TypeControl = (TypeControl + 1) % 13;
443
444 switch(TypeControl)
445 {
446 case 0:
447 OSReport("Particle Type - Points\n");
448 emMain.nType &= ~LINE_SEGMENT;
449 emMain.nType &= ~TEXTURE_PARTICLE;
450 break;
451 case 1:
452 OSReport("Particle Type - Lines\n");
453 emMain.nType |= LINE_SEGMENT;
454 emMain.nType &= ~TEXTURE_PARTICLE;
455 break;
456 case 2:
457 OSReport("Particle Type - Texture Points GX_TO_ZERO\n");
458 emMain.nType &= ~LINE_SEGMENT;
459 emMain.nType |= TEXTURE_PARTICLE;
460 emMain.offset = GX_TO_ZERO;
461 FrameNumber = -16;
462 break;
463 case 3:
464 OSReport("Particle Type - Texture Points GX_TO_SIXTEENTH\n");
465 emMain.nType &= ~LINE_SEGMENT;
466 emMain.nType |= TEXTURE_PARTICLE;
467 emMain.offset = GX_TO_SIXTEENTH;
468 FrameNumber = -16;
469 break;
470 case 4:
471 OSReport("Particle Type - Texture Points GX_TO_EIGHTH\n");
472 emMain.nType &= ~LINE_SEGMENT;
473 emMain.nType |= TEXTURE_PARTICLE;
474 emMain.offset = GX_TO_EIGHTH;
475 FrameNumber = -16;
476 break;
477 case 5:
478 OSReport("Particle Type - Texture Points GX_TO_FOURTH\n");
479 emMain.nType &= ~LINE_SEGMENT;
480 emMain.nType |= TEXTURE_PARTICLE;
481 emMain.offset = GX_TO_FOURTH;
482 FrameNumber = -16;
483 break;
484 case 6:
485 OSReport("Particle Type - Texture Points GX_TO_HALF\n");
486 emMain.nType &= ~LINE_SEGMENT;
487 emMain.nType |= TEXTURE_PARTICLE;
488 emMain.offset = GX_TO_HALF;
489 FrameNumber = -16;
490 break;
491 case 7:
492 OSReport("Particle Type - Texture Points GX_TO_ONE\n");
493 emMain.nType &= ~LINE_SEGMENT;
494 emMain.nType |= TEXTURE_PARTICLE;
495 emMain.offset = GX_TO_ONE;
496 FrameNumber = -16;
497 break;
498 case 8: // bubble texture
499 OSReport("Particle Type - Bubble Points GX_TO_ONE\n");
500 emMain.nType &= ~LINE_SEGMENT;
501 emMain.nType |= TEXTURE_PARTICLE;
502 emMain.offset = GX_TO_ONE;
503 FrameNumber = -16;
504 break;
505 case 9: // bubble texture
506 OSReport("Particle Type - Bubble Points GX_TO_HALF\n");
507 emMain.nType &= ~LINE_SEGMENT;
508 emMain.nType |= TEXTURE_PARTICLE;
509 emMain.offset = GX_TO_HALF;
510 FrameNumber = -16;
511 break;
512 case 10: // bubble texture
513 OSReport("Particle Type - Bubble Points GX_TO_FOURTH\n");
514 emMain.nType &= ~LINE_SEGMENT;
515 emMain.nType |= TEXTURE_PARTICLE;
516 emMain.offset = GX_TO_FOURTH;
517 FrameNumber = -16;
518 break;
519 case 11: // bubble texture
520 OSReport("Particle Type - Bubble Points GX_TO_EIGHTH\n");
521 emMain.nType &= ~LINE_SEGMENT;
522 emMain.nType |= TEXTURE_PARTICLE;
523 emMain.offset = GX_TO_EIGHTH;
524 FrameNumber = -16;
525 break;
526 case 12: // bubble texture
527 OSReport("Particle Type - Bubble Points GX_TO_SIXTEENTH\n");
528 emMain.nType &= ~LINE_SEGMENT;
529 emMain.nType |= TEXTURE_PARTICLE;
530 emMain.offset = GX_TO_SIXTEENTH;
531 FrameNumber = -16;
532 break;
533 }
534 }
535 }
536 else if (CurrentControl == 1)
537 {
538 if (buttons & PAD_BUTTON_B)
539 {
540 SizeAnimationControl = (SizeAnimationControl + 1) % 3;
541
542 switch (SizeAnimationControl)
543 {
544 case 0:
545 OSReport("Point Size - Constant per particle\n");
546 emMain.nType &= ~ANIMATE_SIZE;
547 break;
548 case 1:
549 OSReport("Point Size - Animated per particle - Grow smaller\n");
550 emMain.nType |= ANIMATE_SIZE;
551 emMain.nType &= ~INVERSE_ANIMATE;
552 break;
553 case 2:
554 OSReport("Point Size - Animated per particle - Grow larger\n");
555 emMain.nType |= ANIMATE_SIZE;
556 emMain.nType |= INVERSE_ANIMATE;
557 break;
558 }
559 }
560 }
561
562
563 if (buttons & PAD_TRIGGER_L) {
564 emMain.rSizeMean -= 10.0F;
565 }
566 if (buttons & PAD_TRIGGER_R) {
567 emMain.rSizeMean += 10.0F;
568 }
569
570 if (emMain.rSizeMean < SIZE_MEAN)
571 emMain.rSizeMean = SIZE_MEAN;
572 if (emMain.rSizeMean > SIZE_MAX)
573 emMain.rSizeMean = SIZE_MAX;
574
575 if (buttons & PAD_TRIGGER_L) {
576 OSReport("average size is %5.2f\n", emMain.rSizeMean*0.10F);
577 }
578 if (buttons & PAD_TRIGGER_R) {
579 OSReport("average size is %5.2f\n", emMain.rSizeMean*0.10F);
580 }
581 }
582
srnd(u32 x)583 static void srnd( u32 x )
584 {
585 seed = x;
586 }
587
rndi(void)588 static u32 rndi( void )
589 {
590
591 seed = (seed * 1592653589UL) + 453816691UL;
592 return seed;
593 }
594
rndf(void)595 static double rndf( void ) // 0 <= rndf() < 1
596 {
597 return (1.0 / (ULONG_MAX + 1.0)) * rndi();
598 }
599
600 /*---------------------------------------------------------------------------*
601 Name: Box Muller
602
603 Description: Generates normal random variants
604 via the Box-Muller transform
605
606 Arguments: none
607
608 Returns: normal variant with mean 0 and standard deviation 1
609 *---------------------------------------------------------------------------*/
BoxMuller(void)610 static double BoxMuller( void )
611 {
612 double x1, x2, w, y1;
613 static double y2;
614 static int use_last = 0;
615
616 if (use_last) // use value from previous call
617 {
618 y1 = y2;
619 use_last = 0;
620 }
621 else
622 {
623 do
624 {
625 x1 = 2.0 * rndf() - 1.0;
626 x2 = 2.0 * rndf() - 1.0;
627 w = (x1 * x1) + (x2 * x2);
628 } while ( w > 1.0 );
629
630 w = sqrt( (-2.0 * log( w ) ) / w );
631 y1 = x1 * w;
632 y2 = x2 * w;
633 use_last = 1;
634 }
635 return y1;
636 }
637
638 /*---------------------------------------------------------------------------*
639 Name: ParticleInit
640
641 Description: Initializes the particle list pointers
642
643 Arguments: none
644
645 Returns: none
646 *---------------------------------------------------------------------------*/
ParticleInit(void)647 static void ParticleInit( void )
648 {
649 u32 nI;
650
651 for(nI=1; nI<(MAX_PARTICLES+2); nI++)
652 {
653 ParticleData[nI-1].nNext = nI;
654 }
655 ParticleData[nI-1].nNext = 0;
656
657 nNumActiveParticles = 0;
658
659 TextureParticlesInit();
660 }
661
662 /*---------------------------------------------------------------------------*
663 Name: TextureParticleEmit
664
665 Description: Emits a texture particle, fixing its initial velocity so
666 that it hits the target spot at a specified time
667
668 Arguments: Particle *prt Pointer to the particle to be emitted
669 Emitter *em Pointer to the Emitter
670 s16 nOrder
671
672 Returns: none
673 *---------------------------------------------------------------------------*/
TextureParticleEmit(Particle * prt,Emitter * em,s16 nOrder)674 static void TextureParticleEmit(Particle *prt, Emitter *em, s16 nOrder)
675 {
676 s32 nX = ((nOrder >> 4) & 0x3e) - 31;
677 s32 nY = ((nOrder << 1) & 0x3e) - 31;
678 s32 nT;
679 f32 rRT; // Reciprocal of target time
680 Vec vDest;
681
682 prt->nLifespan = 200;
683
684 vDest.x = nX * 5.0F;
685 vDest.z = nY * 5.0F;
686 vDest.y = 200.0F;
687
688 MTXMultVec(em->mModel, &vDest, &vDest);
689
690 nT = EVENT_TIME - FrameNumber;
691 rRT = 1.0F / nT;
692
693 prt->vVel.x = vDest.x * rRT;
694 prt->vVel.y = (vDest.y * rRT) - (GRAVITY * 0.5F * (nT - 1));
695 prt->vVel.z = vDest.z * rRT;
696
697 prt->vPos.x = 0.0F;
698 prt->vPos.y = 0.0F;
699 prt->vPos.z = 0.0F;
700
701 prt->tex_s = (nX * 0.015625F) + 0.5F;
702 prt->tex_t = (nY * -0.015625F) + 0.5F;
703
704 prt->nType = em->nType;
705
706 if (em->nType & PARTICLE_HAS_SIZE)
707 {
708 prt->nSize = (s32)((float)em->rSizeMean +
709 ((float)BoxMuller() * em->rSizeStdDev));
710 }
711 }
712
713 /*---------------------------------------------------------------------------*
714 Name: ParticleEmit
715
716
717 Description: Emits a single particle, setting up its initial parameters
718
719 Arguments: Particle *prt Pointer to the particle to be emitted
720 Emitter *em Pointer to the Emitter
721
722 Returns: none
723 *---------------------------------------------------------------------------*/
ParticleEmit(Particle * prt,Emitter * em)724 static void ParticleEmit(Particle *prt, Emitter *em)
725 {
726 float rW;
727
728 // Normally distributed life spans
729 prt->nLifespan = (s32)((float)em->rLifespanMean +
730 ((float)BoxMuller() * em->rLifespanStdDev));
731
732 do
733 {
734 prt->vVel.x = (float)(2 * rndf())-1;
735 prt->vVel.z = (float)(2 * rndf())-1;
736 rW = (prt->vVel.x * prt->vVel.x) +
737 (prt->vVel.z * prt->vVel.z);
738 }
739 while (rW > 1.0F);
740
741 prt->vVel.x *= 0.5;
742 prt->vVel.z *= 0.5;
743 rW *= 0.25;
744
745 prt->vVel.y = (float) sqrt( 1.0F - rW );
746
747 VECScale(&prt->vVel, &prt->vVel, PARTICLE_SPEED);
748 MTXMultVec(em->mModel, &prt->vVel, &prt->vVel);
749
750 VECScale(&prt->vVel, &prt->vPos, 2.5F);
751 VECAdd(&em->vPos, &prt->vPos, &prt->vPos);
752
753 prt->nType = em->nType;
754
755 if (em->nType & PARTICLE_HAS_SIZE)
756 {
757 prt->nSize = (s32)((float)em->rSizeMean +
758 ((float)BoxMuller() * em->rSizeStdDev));
759 }
760 }
761
762 /*---------------------------------------------------------------------------*
763 Name: EmitterEmit
764
765 Description: Emits one timestep's worth of particles
766
767 Arguments: Emitter *em Pointer to the emitter
768
769 Returns: none
770 *---------------------------------------------------------------------------*/
EmitterEmit(Emitter * em)771 static void EmitterEmit(Emitter *em)
772 {
773 u32 nPrt, nI, nN, nTmp;
774
775 nN = (u32)em->rEmitted;
776 em->rEmitted += em->rEmissionRate;
777 nN = (u32)em->rEmitted - nN;
778
779
780 if (em->nType & TEXTURE_PARTICLE)
781 {
782 FrameNumber++;
783
784 if (FrameNumber > 250)
785 {
786 FrameNumber = 0;
787 }
788 else if ((FrameNumber < 0) || (FrameNumber > 128))
789 {
790 return;
791 }
792 nN = 8;
793 }
794
795 nNumActiveParticles += nN;
796
797 if (nNumActiveParticles > MAX_PARTICLES)
798 {
799 nN -= (nNumActiveParticles - MAX_PARTICLES);
800 nNumActiveParticles = MAX_PARTICLES;
801 OSReport("Particles Maxed Out!\n");
802 }
803
804 if (nN == 0)
805 {
806 return;
807 }
808
809 nPrt = FREE_LIST;
810
811 if (em->nType & TEXTURE_PARTICLE)
812 {
813 for(nI=0; nI<nN; nI++)
814 {
815 nPrt = ParticleData[nPrt].nNext;
816 TextureParticleEmit(&ParticleData[nPrt], em,
817 GridOrder[(FrameNumber<<3) + nI]);
818 }
819 }
820 else
821 {
822 for(nI=0; nI<nN; nI++)
823 {
824 nPrt = ParticleData[nPrt].nNext;
825 ParticleEmit(&ParticleData[nPrt], em);
826 }
827 }
828
829 nTmp = ParticleData[ACTIVE_LIST].nNext;
830 ParticleData[ACTIVE_LIST].nNext = ParticleData[FREE_LIST].nNext;
831 ParticleData[FREE_LIST].nNext = ParticleData[nPrt].nNext;
832 ParticleData[nPrt].nNext = nTmp;
833 }
834
835 /*---------------------------------------------------------------------------*
836 Name: ParticleUpdate
837
838 Description: Updates the positions of all particles
839
840 Arguments: none
841
842 Returns: none
843 *---------------------------------------------------------------------------*/
ParticleUpdate(void)844 static void ParticleUpdate( void )
845 {
846 /* The particles are stored in a list
847 * Sequentially step through the list.
848 * Decrement lifespan by 1 and if it becomes <0, remove the particle.
849 * Update the position of remaining particles by adding velocity to position
850 * Update the velocity of particles due to any force fields such as gravity
851 */
852
853 u32 nPrt, *pPrev;
854 Particle *prt;
855
856 pPrev = &ParticleData[ACTIVE_LIST].nNext;
857 nPrt = *pPrev;
858 for(; nPrt!=FREE_LIST; nPrt=*pPrev)
859 {
860 prt = &ParticleData[nPrt];
861 prt->nLifespan --;
862
863 if (prt->nLifespan < 0)
864 {
865 // Remove particle from active list and add to head of free list
866
867 *pPrev = prt->nNext;
868 prt->nNext = ParticleData[FREE_LIST].nNext;
869 ParticleData[FREE_LIST].nNext = nPrt;
870
871 nNumActiveParticles--;
872 }
873 else
874 {
875 // Update particle
876
877 VECAdd(&prt->vPos, &prt->vVel, &prt->vPos);
878 prt->vVel.y += GRAVITY;
879 pPrev = &prt->nNext;
880 }
881 }
882 }
883
884 /*---------------------------------------------------------------------------*/
SendParticlePoint(Vec * vPoint,u8 colorIndex)885 static inline void SendParticlePoint( Vec *vPoint, u8 colorIndex )
886 {
887 GXPosition3f32(vPoint->x, vPoint->y, vPoint->z);
888 GXColor1x8(colorIndex);
889 GXTexCoord2f32( 0.5F, 0.5F ); //texCoordIndex);
890 }
891
892 /*---------------------------------------------------------------------------*/
SendParticleLine(Vec * vPoint1,Vec * vDelta,u8 colorIndex)893 static inline void SendParticleLine( Vec *vPoint1, Vec *vDelta, u8 colorIndex )
894 {
895 GXPosition3f32(vPoint1->x - vDelta->x * 2,
896 vPoint1->y - vDelta->y * 2,
897 vPoint1->z - vDelta->z * 2);
898 GXColor1x8(colorIndex);
899 GXTexCoord2f32( 0.0F, 0.0F ); //texCoordIndex);
900
901 GXPosition3f32(vPoint1->x + vDelta->x * 2,
902 vPoint1->y + vDelta->y * 2,
903 vPoint1->z + vDelta->z * 2);
904 GXColor1x8(colorIndex);
905 GXTexCoord2f32( 0.0F, 1.0F ); //texCoordIndex);
906 }
907
908 /*---------------------------------------------------------------------------*/
SendTexturePoint(Vec * vPoint,f32 s,f32 t)909 static inline void SendTexturePoint( Vec *vPoint, f32 s, f32 t )
910 {
911 GXPosition3f32(vPoint->x, vPoint->y, vPoint->z);
912 GXColor1x8(16);
913 GXTexCoord2f32(s, t);
914 }
915
916 /*---------------------------------------------------------------------------*
917 Name: DrawParticles
918
919 Description: Draws the particles
920
921 Arguments: none
922
923 Returns: none
924 *---------------------------------------------------------------------------*/
DrawParticles(void)925 static void DrawParticles( void )
926 {
927 Mtx mv;
928 u32 nPrt;
929 Particle *prt;
930
931 GXClearVtxDesc();
932
933 // Set Position Params
934
935 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, PositionShift);
936 GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
937 GXSetArray(GX_VA_POS, &ParticleData[0].vPos, sizeof(Particle));
938
939 // Set Color Params
940
941 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
942 GXSetVtxDesc(GX_VA_CLR0, GX_INDEX8);
943 GXSetArray(GX_VA_CLR0, ColorRGBA8, 4);
944
945 GXSetNumChans(1);
946 GXSetChanCtrl(
947 GX_COLOR0,
948 GX_FALSE, // enable channel
949 GX_SRC_VTX, // amb source
950 GX_SRC_VTX, // mat source
951 GX_LIGHT0, // light mask
952 GX_DF_CLAMP,// diffuse function
953 GX_AF_NONE);
954 GXSetChanCtrl(
955 GX_ALPHA0,
956 GX_FALSE, // enable channel
957 GX_SRC_VTX, // amb source
958 GX_SRC_VTX, // mat source
959 GX_LIGHT0, // light mask
960 GX_DF_CLAMP,// diffuse function
961 GX_AF_NONE);
962
963 // Set Tex Coord Params
964 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
965
966 // 8-bit indexed floating point texture coordinates
967 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, TexCoordShift);
968 GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
969
970 // Setup Transforms
971
972 MTXCopy(v, mv);
973 GXLoadPosMtxImm(mv, GX_PNMTX0);
974 MTXInverse(mv, mv);
975 MTXTranspose(mv, mv);
976 GXLoadNrmMtxImm(mv, GX_PNMTX0);
977
978 nPrt = ParticleData[ACTIVE_LIST].nNext;
979
980 //GXBegin(GX_POINTS, GX_VTXFMT0, (u16)nNumActiveParticles);
981 for(; nPrt!=FREE_LIST; nPrt=prt->nNext)
982 {
983 prt = &ParticleData[nPrt];
984
985 if (prt->nType & TEXTURE_PARTICLE)
986 {
987 GXSetNumTexGens(1); // Use one texture coord
988 // undocumented function
989 GXEnableTexOffsets(GX_TEXCOORD0, GX_FALSE, GX_TRUE);
990
991 if (TypeControl >= 8 && TypeControl <= 12) // Bubble texture
992 {
993 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0);
994 GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
995 }
996 else
997 {
998 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
999 GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE);
1000 }
1001
1002 if (prt->nType & ANIMATE_SIZE)
1003 {
1004 if (prt->nType & INVERSE_ANIMATE)
1005 {
1006 GXSetPointSize((u8)((prt->nSize + 200 - prt->nLifespan)*0.10), emMain.offset);
1007 }
1008 else
1009 {
1010 GXSetPointSize((u8)((prt->nSize + prt->nLifespan)*0.10), emMain.offset);
1011 }
1012 }
1013 else
1014 {
1015 GXSetPointSize((u8)(prt->nSize*0.10), emMain.offset);
1016 }
1017 GXBegin(GX_POINTS, GX_VTXFMT0, 1);
1018 if (TypeControl >= 8 && TypeControl <= 12)
1019 SendTexturePoint( &prt->vPos, 0.0F, 0.0F );
1020 else
1021 SendTexturePoint( &prt->vPos, prt->tex_s, prt->tex_t );
1022 GXEnd();
1023 continue;
1024 }
1025
1026 GXSetNumTexGens(0); // Use no texture (coord)
1027 GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
1028 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
1029
1030 if (prt->nType & LINE_SEGMENT)
1031 {
1032 if (prt->nType & PARTICLE_HAS_SIZE)
1033 {
1034 if (prt->nType & ANIMATE_SIZE)
1035 {
1036 if (prt->nType & INVERSE_ANIMATE)
1037 {
1038 GXSetLineWidth((u8)((prt->nSize + 200 - prt->nLifespan)*0.05), GX_TO_ZERO);
1039 }
1040 else
1041 {
1042 GXSetLineWidth((u8)((prt->nSize + prt->nLifespan)*0.05), GX_TO_ZERO);
1043 }
1044 }
1045 else
1046 {
1047 GXSetLineWidth((u8)(prt->nSize*0.06), GX_TO_ZERO);
1048 }
1049 }
1050
1051 GXBegin(GX_LINES, GX_VTXFMT0, 2);
1052 SendParticleLine(&prt->vPos, &prt->vVel, (u8)(prt->nLifespan>>3));
1053 GXEnd();
1054 }
1055 else
1056 {
1057 if (prt->nType & PARTICLE_HAS_SIZE)
1058 {
1059 if (prt->nType & ANIMATE_SIZE)
1060 {
1061 if (prt->nType & INVERSE_ANIMATE)
1062 {
1063 GXSetPointSize((u8)((prt->nSize + 200 - prt->nLifespan)*0.10), GX_TO_ZERO);
1064 }
1065 else
1066 {
1067 GXSetPointSize((u8)((prt->nSize + prt->nLifespan)*0.10), GX_TO_ZERO);
1068 }
1069 }
1070 else
1071 {
1072 GXSetPointSize((u8)(prt->nSize*0.12), GX_TO_ZERO);
1073 }
1074 }
1075 GXBegin(GX_POINTS, GX_VTXFMT0, 1);
1076 SendParticlePoint(&prt->vPos, (u8)(prt->nLifespan>>3));
1077 GXEnd();
1078 }
1079 }
1080 //GXEnd();
1081 }
1082
1083 /*---------------------------------------------------------------------------*
1084 Name: EmitterInit
1085
1086 Description: Initializes an emitter
1087
1088 Arguments: Emitter *em Pointer to the emitter
1089
1090 Returns: none
1091 *---------------------------------------------------------------------------*/
EmitterInit(Emitter * em)1092 static void EmitterInit( Emitter *em )
1093 {
1094 MTXIdentity( em->mModel );
1095 em->vLinVel.x = em->vLinVel.y = em->vLinVel.z = 0.0F;
1096 em->vAngVel = em->vLinVel;
1097 em->rEmitted = 0.0F;
1098 em->rEmissionRate = EMMISION_RATE;
1099 em->rLifespanMean = LIFESPAN_MEAN;
1100 em->rLifespanStdDev = LIFESPAN_STDDEV;
1101 em->rSizeMean = SIZE_MEAN;
1102 em->rSizeStdDev = SIZE_STDDEV;
1103 em->nType = PARTICLE_HAS_SIZE;
1104 }
1105
1106 /*---------------------------------------------------------------------------*
1107 Name: EmitterUpdate
1108
1109 Description: Emits one timestep's worth of particles
1110
1111 Arguments: Emitter *em Pointer to the emitter
1112 s8 rotX amount to rotate about vertical
1113 s8 rotY amount to rotate about horizontal
1114
1115 Returns: none
1116 *---------------------------------------------------------------------------*/
EmitterUpdate(Emitter * em,s8 rotX,s8 rotY)1117 static void EmitterUpdate(Emitter *em, s8 rotX, s8 rotY)
1118 {
1119 Vec vVertical;
1120 Vec vHorizontal;
1121 Mtx m;
1122
1123 vVertical.x = v[0][1];
1124 vVertical.y = v[1][1];
1125 vVertical.z = v[2][1];
1126
1127 vHorizontal.x = v[0][0];
1128 vHorizontal.y = v[1][0];
1129 vHorizontal.z = v[2][0];
1130
1131 MTXRotAxisDeg(m, &vHorizontal, (float)rotY * 0.01F);
1132 MTXConcat(m, em->mModel, em->mModel);
1133 MTXRotAxisDeg(m, &vVertical, (float)rotX * 0.01F);
1134 MTXConcat(m, em->mModel, em->mModel);
1135 }
1136
1137 /*---------------------------------------------------------------------------*/
SendEmitterVertex(u8 posIndex,u8 normalIndex,u8 colorIndex,u8 texCoordIndex)1138 static void SendEmitterVertex ( u8 posIndex, u8 normalIndex,
1139 u8 colorIndex, u8 texCoordIndex )
1140 {
1141 GXPosition1x8(posIndex);
1142 GXNormal1x8(normalIndex);
1143 GXColor1x8(colorIndex);
1144 GXTexCoord1x8(texCoordIndex);
1145 }
1146
1147 /*---------------------------------------------------------------------------*
1148 Name: DrawEmitter
1149
1150 Description: Draws the emitter
1151
1152 Arguments: none
1153
1154 Returns: none
1155 *---------------------------------------------------------------------------*/
DrawEmitter(void)1156 static void DrawEmitter( void )
1157 {
1158 Mtx mv;
1159
1160 GXClearVtxDesc();
1161
1162 // Set Position Params
1163
1164 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, PositionShift);
1165 GXSetVtxDesc(GX_VA_POS, GX_INDEX8);
1166 GXSetArray(GX_VA_POS, FloatVert, 12);
1167
1168 // Set Normal Params
1169
1170 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_NRM_XYZ, GX_F32, NormalShift);
1171 GXSetVtxDesc(GX_VA_NRM, GX_INDEX8);
1172 GXSetArray(GX_VA_NRM, FloatNorm, 12);
1173 /*
1174 GXSetChanCtrl(
1175 GX_COLOR0,
1176 GX_FALSE, // enable channel
1177 GX_SRC_VTX, // amb source
1178 GX_SRC_VTX, // mat source
1179 GX_LIGHT0, // light mask
1180 GX_DF_CLAMP,// diffuse function
1181 GX_AF_NONE);
1182 */
1183
1184 // Set Color Params
1185
1186 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
1187 GXSetVtxDesc(GX_VA_CLR0, GX_INDEX8);
1188 GXSetArray(GX_VA_CLR0, EmitterColorRGBA8, 4);
1189
1190 GXSetNumChans(1);
1191 GXSetChanCtrl(
1192 GX_COLOR0,
1193 GX_FALSE, // enable channel
1194 GX_SRC_VTX, // amb source
1195 GX_SRC_VTX, // mat source
1196 GX_LIGHT0, // light mask
1197 GX_DF_CLAMP,// diffuse function
1198 GX_AF_NONE);
1199 GXSetChanCtrl(
1200 GX_ALPHA0,
1201 GX_FALSE, // enable channel
1202 GX_SRC_REG, // amb source
1203 GX_SRC_VTX, // mat source
1204 GX_LIGHT0, // light mask
1205 GX_DF_CLAMP,// diffuse function
1206 GX_AF_NONE);
1207
1208 // Set Tex Coord Params
1209 GXSetNumTexGens( 1 );
1210 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
1211 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
1212 GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE); //GX_REPLACE);
1213
1214
1215 // 8-bit indexed floating point texture coordinates
1216 GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, TexCoordShift);
1217 GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8);
1218 GXSetArray(GX_VA_TEX0, FloatTex, 8);
1219
1220 // Setup Transforms
1221
1222 MTXConcat(v, emMain.mModel, mv);
1223 emMain.mModel[0][3] = emMain.vPos.x;
1224 emMain.mModel[1][3] = emMain.vPos.y;
1225 emMain.mModel[2][3] = emMain.vPos.z;
1226 GXLoadPosMtxImm(mv, GX_PNMTX0);
1227 MTXInverse(mv, mv);
1228 MTXTranspose(mv, mv);
1229 GXLoadNrmMtxImm(mv, GX_PNMTX0);
1230
1231 // Send Geometry
1232
1233 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1234
1235 SendEmitterVertex(1, 0, 2, 1);
1236 SendEmitterVertex(2, 0, 2, 2);
1237 SendEmitterVertex(0, 0, 1, 0);
1238 SendEmitterVertex(3, 0, 1, 3);
1239
1240 GXEnd();
1241
1242 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1243
1244 SendEmitterVertex(5, 1, 3, 1);
1245 SendEmitterVertex(6, 1, 4, 2);
1246 SendEmitterVertex(4, 1, 3, 0);
1247 SendEmitterVertex(7, 1, 4, 3);
1248
1249 GXEnd();
1250
1251 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1252
1253 SendEmitterVertex(6, 2, 0, 1);
1254 SendEmitterVertex(5, 2, 0, 2);
1255 SendEmitterVertex(2, 2, 0, 0);
1256 SendEmitterVertex(3, 2, 0, 3);
1257
1258 GXEnd();
1259
1260 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1261
1262 SendEmitterVertex(0, 3, 5, 1);
1263 SendEmitterVertex(4, 3, 5, 2);
1264 SendEmitterVertex(1, 3, 5, 0);
1265 SendEmitterVertex(7, 3, 5, 3);
1266
1267 GXEnd();
1268
1269 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1270
1271 SendEmitterVertex(4, 4, 3, 1);
1272 SendEmitterVertex(0, 4, 1, 2);
1273 SendEmitterVertex(5, 4, 3, 0);
1274 SendEmitterVertex(3, 4, 1, 3);
1275
1276 GXEnd();
1277
1278 GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, 4);
1279
1280 SendEmitterVertex(2, 5, 2, 1);
1281 SendEmitterVertex(1, 5, 2, 2);
1282 SendEmitterVertex(6, 5, 4, 0);
1283 SendEmitterVertex(7, 5, 4, 3);
1284
1285 GXEnd();
1286 }
1287
1288 /*---------------------------------------------------------------------------*
1289 Name: AnimTick
1290
1291 Description: Updates the objects in the world by one timestep
1292
1293 Arguments: none
1294
1295 Returns: none
1296 *---------------------------------------------------------------------------*/
AnimTick(void)1297 static void AnimTick ( void )
1298 {
1299 u16 buttons = DEMOPadGetButton(0);
1300 u16 down = DEMOPadGetButtonDown(0);
1301 s8 stickX = DEMOPadGetStickX(0);
1302 s8 stickY = DEMOPadGetStickY(0);
1303
1304 EmitterUpdate(&emMain, stickX, stickY);
1305
1306 if (!(buttons & PAD_BUTTON_A))
1307 {
1308 EmitterEmit(&emMain);
1309 ParticleUpdate();
1310 }
1311
1312 CameraUpdate(0, 0);
1313 Options(down);
1314 }
1315
1316 /*---------------------------------------------------------------------------*
1317 Name: DrawTick
1318
1319 Description: Draw the current model once.
1320
1321 Arguments: v view matrix
1322 m model matrix
1323
1324 Returns: none
1325 *---------------------------------------------------------------------------*/
DrawTick(void)1326 static void DrawTick( void )
1327 {
1328 DrawEmitter();
1329 DrawParticles();
1330 }
1331
1332 /*---------------------------------------------------------------------------*
1333 Name: PrintIntro
1334
1335 Description: Prints the directions on how to use this demo.
1336
1337 Arguments: none
1338
1339 Returns: none
1340 *---------------------------------------------------------------------------*/
PrintIntro(void)1341 static void PrintIntro( void )
1342 {
1343 OSReport("\n\n****************************************************\n");
1344 OSReport(" geo-particle - demonstrate paritcle effects\n");
1345 OSReport("****************************************************\n");
1346 OSReport("BUTTON X: Particle Type/Particle Animation control mode\n");
1347 OSReport("BUTTON B: Select a type or animation\n");
1348 OSReport("BUTTON A: Pause animation\n");
1349 OSReport("\n");
1350 OSReport("TRIGGER R: Increase average size of points\n");
1351 OSReport("TRIGGER L: Decrease average size of points\n");
1352 OSReport("\n");
1353 OSReport("to quit hit the menu button\n");
1354 OSReport("*******************************************************\n");
1355 OSReport("\n\n");
1356 }
1357
1358
1359