1 /*---------------------------------------------------------------------------*
2 Project: WPAD Health Demo Program
3 File: handling_weight.c
4
5 Copyright (C) 2007 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 $Log: handling_weight.c,v $
14 Revision 1.4.2.1 2008/08/22 04:44:30 urata
15 Added WPADRegisterBLCWorkarea so that it would run correctly in jp-mode.
16
17 Revision 1.4 2008/06/17 08:03:33 tojo
18 Removed unused headers.
19
20 Revision 1.3 2008/04/23 04:05:48 tojo
21 Updated WBCGetTGCWeight to latest spec.
22
23 Revision 1.2 2008/04/17 05:25:33 tojo
24 Changed API for WBCSetupCalibration.
25
26 Revision 1.1 2008/04/17 02:55:42 tojo
27 Initial check-in.
28
29 *---------------------------------------------------------------------------*/
30
31 #include <revolution.h>
32 #include <revolution/wpad.h>
33 #include <revolution/wpadBalance.h>
34 #include <revolution/wbc.h>
35
36 #define DEMO_USE_MEMLIB=1 // This turns on the DEMO library's MEM heaps.
37 #include <demo.h>
38
39 /*---------------------------------------------------------------------------*
40 * Local Definitions
41 *---------------------------------------------------------------------------*/
42
43 #define SCREEN_WIDTH 320
44 #define SCREEN_HEIGHT 240
45 #define FONT_HEIGHT 8
46
47 GXColor smoke_clr = { 61, 61, 61, 255 } ;
48 GXColor red_clr = { 237, 28, 36, 255 } ;
49 GXColor blue_clr = { 0, 84,166, 255 } ;
50 GXColor yellow_clr = { 255,242, 0, 255 } ;
51 GXColor peagreen_clr = { 141,198, 63, 255 } ;
52
53 WPADBLStatus status;
54
55
56 u8 rxBuf[128] ATTRIBUTE_ALIGN(32);
57 u8 txBuf[128] ATTRIBUTE_ALIGN(32);
58
59
60 /*---------------------------------------------------------------------------*
61 * Function prototypes
62 *---------------------------------------------------------------------------*/
63
64
65 // MEM2 memory allocation routines. The application must provide these to
66 // WPAD, so it can setup the data transfer buffer. This buffer must reside
67 // in MEM2.
68 static void *myAlloc ( u32 size );
69 static u8 myFree ( void *ptr );
70
71
72 // Callbacks for CONNECT and EXTENSION events.
73 void connectCallback ( s32 chan, s32 reason );
74 void extensionCallback ( s32 chan, s32 result );
75
76 // Internal functions
77 static void initialize ( void );
78 static void renderStatus ( void );
79 static void init_draw_graphic ( u16 fb_width, u16 fb_height );
80 static void draw_line ( f32 x1, f32 y1, f32 x2, f32 y2, GXColor clr, f32 width );
81 static void draw_box ( f32 x1, f32 y1, f32 x2, f32 y2, GXColor clr, f32 width );
82
83 static double zero_point[WPAD_PRESS_UNITS];
84 static double sum;
85 static double ave;
86 static u32 counter;
87 static BOOL get_weight_flag = FALSE;
88 static BOOL set_zero_flag = FALSE;
89
90 static u8 workarea[WPAD_BLCINT_WORK_LEN] ATTRIBUTE_ALIGN(32);
91
92 #define NSAMPLES_2SEC 120
93
94 /*===========================================================================*
95 * F U N C T I O N D E F I N I T I O N S
96 *===========================================================================*/
97 /*---------------------------------------------------------------------------*
98 * Name : main()
99 * Description :
100 * Arguments : None.
101 * Returns : None.
102 *---------------------------------------------------------------------------*/
ctrlWbc(s32 chan,s32 result)103 static void ctrlWbc( s32 chan, s32 result )
104 {
105 OSReport("chan%d [%d]\n", chan, result);
106 }
107
WaitMilliTime(s32 msec)108 static void WaitMilliTime(s32 msec)
109 {
110 OSTime t=OSGetTime();
111 while(OSTicksToMilliseconds(OSGetTime()-t) < msec)
112 ;
113 }
114
115
ZeroSetStart2(s32 chan,s32 result)116 static void ZeroSetStart2( s32 chan, s32 result )
117 {
118 #pragma unused(chan, result)
119
120 // Wait about 200 ms so that Board's press values rarely become unstable
121 // after updating Balance Wii Board's temperature.
122 WaitMilliTime(200);
123
124
125 // Start Zero point correction.
126 set_zero_flag = TRUE;
127 counter = 0;
128 zero_point[0] = 0;
129 zero_point[1] = 0;
130 zero_point[2] = 0;
131 zero_point[3] = 0;
132 }
133
134
ZeroSetStart(s32 chan,s32 result)135 static void ZeroSetStart( s32 chan, s32 result )
136 {
137 #pragma unused(chan, result)
138
139 // Check Board's temperature.
140 WPADRead(WPAD_CHAN3, &status);
141
142
143 if((status.temp == 127) || (status.temp == -128))
144 {
145 // Update Board's temperature again if you can not get correct Board's temperature.
146 WPADControlBLC(WPAD_CHAN3, WPAD_BLCMD_UPDATE_TEMP, ZeroSetStart2);
147 }
148 else
149 {
150 // Wait about 200 ms so that Board's press values rarely become unstable
151 // after updating Balance Wii Board's temperature.
152 WaitMilliTime(200);
153
154 // Start Zero point correction.
155 set_zero_flag = TRUE;
156 counter = 0;
157 zero_point[0] = 0;
158 zero_point[1] = 0;
159 zero_point[2] = 0;
160 zero_point[3] = 0;
161 }
162 }
163
main(void)164 int main( void )
165 {
166
167 initialize();
168
169 // For the Japanese version only, set the working buffer because it is required when the Wii Balance Board is initialized.
170 //
171 // After library initialization is complete, the working buffer will no longer be used and can be released.
172 //
173 //
174 // For markets outside Japan, no problems will occur if this function is not called.
175 //
176 // Additionally, no problems will occur if it is called.
177 WPADRegisterBLCWorkarea( workarea );
178
179 WPADRegisterAllocator(myAlloc, myFree);
180
181 WPADInit();
182
183 WPADSetConnectCallback(WPAD_CHAN3, connectCallback);
184
185 while (WPAD_STATE_SETUP != WPADGetStatus())
186 {
187 ;
188 }
189
190 while(1)
191 {
192 DEMOPadRead();
193
194 if (DEMOPadGetButtonDown(0) & PAD_BUTTON_A)
195 {
196 // Turn on Board's power.
197 WPADControlBLC(WPAD_CHAN3, WPAD_BLCMD_ON, ctrlWbc);
198 }
199 if (DEMOPadGetButtonDown(0) & PAD_BUTTON_B)
200 {
201 // Turn off Board's power.
202 WPADControlBLC(WPAD_CHAN3, WPAD_BLCMD_OFF, ctrlWbc);
203 }
204 if (DEMOPadGetButtonDown(0) & PAD_TRIGGER_Z)
205 {
206 // Update Board's temperature.
207 WPADControlBLC(WPAD_CHAN3, WPAD_BLCMD_UPDATE_TEMP, ctrlWbc);
208 }
209 if (DEMOPadGetButtonDown(0) & PAD_BUTTON_MENU)
210 {
211 if((set_zero_flag == FALSE) && (get_weight_flag == FALSE))
212 {
213 // Update Board's temperature the eve of zero point correction.
214 WPADControlBLC(WPAD_CHAN3, WPAD_BLCMD_UPDATE_TEMP, ZeroSetStart);
215 }
216 }
217 if (DEMOPadGetButtonDown(0) & PAD_BUTTON_LEFT)
218 {
219 if((set_zero_flag == FALSE) && (get_weight_flag == FALSE))
220 {
221 // Get an average value for 2 seconds
222 get_weight_flag = TRUE;
223 counter = 0;
224 sum = 0;
225 }
226 }
227
228
229 // Caption
230 DEMOInitCaption( DM_FT_XLU, SCREEN_WIDTH, SCREEN_HEIGHT );
231 GXSetZMode( GX_ENABLE, GX_ALWAYS, GX_ENABLE );
232 GXSetBlendMode( GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
233
234 DEMOBeforeRender();
235 renderStatus();
236 DEMODoneRender();
237 }
238
239 } // End main()
240
241 /*---------------------------------------------------------------------------*
242 * Name : connectCallback()
243 *
244 * Description : This callback is invoked when a controller is connected or
245 * disconnected.
246 *
247 * Arguments : The channel (chan) for which the event has occurred.
248 * The channel status (reason):
249 * WPAD_ERR_NONE means a controller has been connected.
250 * WPAD_ERR_NO_CONTROLLER means a controller disconnected.
251 *
252 * Returns : None.
253 *---------------------------------------------------------------------------*/
connectCallback(s32 chan,s32 reason)254 void connectCallback(s32 chan, s32 reason)
255 {
256 u32 type;
257
258 if (reason == WPAD_ERR_NONE)
259 {
260 // Disconnect 4P if 4P is not Wii Balance Board.
261 // 4P is reserved for Wii Balance Board.
262 WPADProbe(chan, &type);
263 if (chan == WPAD_CHAN3 && type != WPAD_DEV_BALANCE_CHECKER)
264 {
265 OSReport("Channel%d is reserved for the balance checker.\n", chan);
266 WPADDisconnect(chan);
267 }
268 else
269 {
270 // Read the calibration value for calculating a weight.
271 if(!WBCSetupCalibration())
272 {
273 OSHalt("WBC FATAL ERROR!!\n");
274 }
275
276 OSReport("Channel%d is connected.\n", chan);
277 WPADSetExtensionCallback(chan, extensionCallback);
278 }
279 }
280 else
281 {
282 OSReport("Channel%d is disconnected.\n", chan);
283 }
284 } // End connectCallback()
285
286 /*---------------------------------------------------------------------------*
287 * Name : extensionCallback()
288 *
289 * Description : This callback is invoked when an Extension has been attached.
290 *
291 * Arguments : The channel (chan) for which the extension event occurred.
292 * The device type (result):
293 *
294 * WPAD_DEV_UNKNOWN means that something has been attached, but
295 * it's being initialized, and we won't know what it is until
296 * initialization is complete.
297 *
298 * WPAD_DEV_CORE means that an extension has been removed and
299 * we're back to just the core device.
300 *
301 * WPAD_DEV_FREESTYLE means that the "NUNCHUK" extension has
302 * been attached and initialized.
303 *
304 * WPAD_DEV_CLASSIC means that the "CLASSIC" extension has been
305 * attached and initialized.
306 *
307 * Returns : None.
308 *---------------------------------------------------------------------------*/
extensionCallback(s32 chan,s32 result)309 void extensionCallback(s32 chan, s32 result)
310 {
311 switch(result)
312 {
313 case WPAD_DEV_UNKNOWN:
314 OSReport("Initializing extension on channel%d...\n", chan);
315 break;
316
317 case WPAD_DEV_CORE:
318 WPADControlDpd(chan, WPAD_DPD_EXP, NULL);
319 WPADSetDataFormat(chan, WPAD_FMT_CORE_ACC_DPD);
320
321 OSReport("Extension removed on channel%d.\n", chan);
322 break;
323
324 case WPAD_DEV_NOT_SUPPORTED:
325 case WPAD_DEV_FUTURE:
326 WPADControlDpd(chan, WPAD_DPD_EXP, NULL);
327 WPADSetDataFormat(chan, WPAD_FMT_CORE_ACC_DPD);
328
329 OSReport("Extension is not useful on channel%d.\n", chan);
330 break;
331
332 case WPAD_DEV_FREESTYLE:
333 WPADControlDpd(chan, WPAD_DPD_STD, NULL);
334 WPADSetDataFormat(chan, WPAD_FMT_FREESTYLE_ACC_DPD);
335
336 OSReport("Freestyle initialized on channel%d.\n", chan);
337 break;
338
339 case WPAD_DEV_CLASSIC:
340 WPADControlDpd(chan, WPAD_DPD_STD, NULL);
341 WPADSetDataFormat(chan, WPAD_FMT_CLASSIC_ACC_DPD);
342
343 OSReport("Classicstyle initialized on channel%d.\n", chan);
344 break;
345
346 case WPAD_DEV_BALANCE_CHECKER:
347 WPADControlDpd(chan, WPAD_DPD_OFF, NULL);
348 WPADSetDataFormat(chan, WPAD_FMT_BALANCE_CHECKER);
349 WPADControlBLC(chan, WPAD_BLCMD_ON, NULL);
350
351 OSReport("Balance checker initialized on channel%d.\n", chan);
352 break;
353
354 default:
355 // Here is WPAD_DEV_NOT_FOUND.
356 // If the controller is disconnected while the extension is initializing
357 // it reaches here. There is nothing to do.
358 break;
359
360 } // End
361
362 } // End extensionCallback()
363
364 /*---------------------------------------------------------------------------*
365 * Name : myAlloc()
366 * Description : Callback needed by WPAD to allocate memory from MEM2 heap.
367 * Arguments : size of block, in bytes.
368 * Returns : pointer to allocated block.
369 *---------------------------------------------------------------------------*/
myAlloc(u32 size)370 static void *myAlloc(u32 size)
371 {
372 void *ptr;
373
374 ptr = MEMAllocFromAllocator(&DemoAllocator2, size);
375 ASSERTMSG(ptr, "Memory allocation failed\n");
376
377 return(ptr);
378
379 } // myAlloc()
380
381 /*---------------------------------------------------------------------------*
382 * Name : myFree()
383 * Description : Callback needed by WPAD to deallocate memory from MEM2 heap.
384 * Arguments : None.
385 * Returns : Always 1.
386 *---------------------------------------------------------------------------*/
myFree(void * ptr)387 static u8 myFree(void *ptr)
388 {
389
390 MEMFreeToAllocator(&DemoAllocator2, ptr);
391
392 // we should ensure that memory is deallocated properly, but oh well
393 return(1);
394
395 } // myFree()
396
397
renderStatus(void)398 static void renderStatus(void)
399 {
400 u32 type;
401 s32 isExist = 0;
402
403 if (WPADProbe(WPAD_CHAN3, &type) == WPAD_ERR_NO_CONTROLLER)
404 {
405 if (WPADIsRegisteredBLC())
406 {
407 DEMOPrintf( 50, 100, 0, "WBC is not connected.");
408 }
409 else
410 {
411 DEMOPrintf( 20, 100, 0, "WBC is not registered.");
412 DEMOPrintf( 20, 110, 0, "Please regist WBC with SYNC button.");
413 }
414 }
415 else
416 {
417 if (type == WPAD_DEV_BALANCE_CHECKER)
418 {
419 double weight[WPAD_PRESS_UNITS];
420 double tgc_weight;
421 double all;
422
423
424 WPADRead(WPAD_CHAN3, &status);
425
426 DEMOPrintf( 95, 40, 0, "%5d", status.press[0]);
427 DEMOPrintf( 95, 110, 0, "%5d", status.press[1]);
428 DEMOPrintf( 10, 40, 0, "%5d", status.press[2]);
429 DEMOPrintf( 10, 110, 0, "%5d", status.press[3]);
430
431 isExist = WBCRead(&status, weight, (u32)(sizeof(weight) / sizeof(weight[0])));
432 all = (double)(weight[0] + weight[1] + weight[2] + weight[3]);
433
434
435
436 if((get_weight_flag || set_zero_flag) && counter < NSAMPLES_2SEC)
437 {
438 counter++;
439 sum += all;
440 zero_point[0] += status.press[0];
441 zero_point[1] += status.press[1];
442 zero_point[2] += status.press[2];
443 zero_point[3] += status.press[3];
444 }
445
446
447 if(counter == NSAMPLES_2SEC)
448 {
449 ave = sum / NSAMPLES_2SEC;
450 zero_point[0] /= NSAMPLES_2SEC;
451 zero_point[1] /= NSAMPLES_2SEC;
452 zero_point[2] /= NSAMPLES_2SEC;
453 zero_point[3] /= NSAMPLES_2SEC;
454 get_weight_flag = FALSE;
455
456 if(set_zero_flag)
457 {
458 // Set the zero point into WBC library.
459 if(0 == WBCSetZEROPoint( zero_point,(u32)(sizeof(zero_point) / sizeof(zero_point[0])) ))
460 {
461 // Success
462 set_zero_flag = FALSE;
463 }
464 else
465 {
466 // Retry if failed.
467 counter = 0;
468 zero_point[0] = 0;
469 zero_point[1] = 0;
470 zero_point[2] = 0;
471 zero_point[3] = 0;
472 }
473 }
474 }
475
476 WBCGetTGCWeight(ave, &tgc_weight, &status);
477
478 if(get_weight_flag || set_zero_flag)
479 {
480 DEMOPrintf( 95, 55, 0, "wait...");
481 DEMOPrintf( 95, 125, 0, "wait...");
482 DEMOPrintf( 10, 55, 0, "wait...");
483 DEMOPrintf( 10, 125, 0, "wait...");
484
485 if(get_weight_flag)
486 {
487 DEMOPrintf(175, 60, 0, "Wt :WAIT");
488 DEMOPrintf(175, 70, 0, "Wt(TGC):WAIT");
489 }
490 else
491 {
492 DEMOPrintf(175, 60, 0, "Wt :%3.1f[kg]", ave);
493 DEMOPrintf(175, 70, 0, "Wt(TGC):%3.1f[kg]", tgc_weight);
494 }
495 }
496 else
497 {
498 DEMOPrintf( 95, 55, 0, "%3.1f", weight[0]);
499 DEMOPrintf( 95, 125, 0, "%3.1f", weight[1]);
500 DEMOPrintf( 10, 55, 0, "%3.1f", weight[2]);
501 DEMOPrintf( 10, 125, 0, "%3.1f", weight[3]);
502 DEMOPrintf(175, 60, 0, "Wt :%3.1f[kg]", ave);
503 DEMOPrintf(175, 70, 0, "Wt(TGC):%3.1f[kg]", tgc_weight);
504 DEMOPrintf(175, 80, 0, "Setup :%s", WBCGetCalibrationStatus()==TRUE ? "OK":"NG");
505 }
506
507 DEMOPrintf(175, 20, 0, "Temp %d", status.temp);
508 DEMOPrintf(175, 30, 0, "Batt %d/Level %d", status.battery, WBCGetBatteryLevel(status.battery));
509 DEMOPrintf(175, 40, 0, "IsEXIST = %s", isExist == 1 ? "EXIST" : "NONE");
510
511 DEMOPrintf( 10, 160, 0, "A: Turn On SENSOR");
512 DEMOPrintf( 10, 170, 0, "B: Turn Off SENSOR");
513 DEMOPrintf( 10, 180, 0, "MENU: Set ZERO point");
514 DEMOPrintf( 10, 190, 0, "LEFT: Get AVE Weight");
515 DEMOPrintf( 10, 220, 0, "Z: Update the current temperature");
516
517 init_draw_graphic(SCREEN_WIDTH, SCREEN_HEIGHT);
518 draw_box(-143, -100, -68, -50, peagreen_clr, 2);
519 init_draw_graphic(SCREEN_WIDTH, SCREEN_HEIGHT);
520 draw_box(-143, -30, -68, 20, yellow_clr, 2);
521 init_draw_graphic(SCREEN_WIDTH, SCREEN_HEIGHT);
522 draw_box(-63, -100, 12, -50, blue_clr, 2);
523 init_draw_graphic(SCREEN_WIDTH, SCREEN_HEIGHT);
524 draw_box(-63, -30, 12, 20, red_clr, 2);
525 }
526 else
527 {
528 DEMOPrintf(50, 100, 0, "No balance checker is attached.");
529 }
530 }
531
532 } // End renderStatus()
533
534
initialize(void)535 static void initialize( void )
536 {
537 OSInit();
538
539 DEMOInit( &GXNtsc480IntDf );
540
541 GXSetCopyClear( smoke_clr, GX_MAX_Z24 );
542 GXCopyDisp( DEMOGetCurrentBuffer(), GX_TRUE );
543
544 DEMOPadInit();
545 } // End
546
547
init_draw_graphic(u16 fb_width,u16 fb_height)548 static void init_draw_graphic( u16 fb_width, u16 fb_height )
549 {
550 Mtx44 proj_mtx ;
551 Mtx view_mtx ;
552 f32 canvas_wd, canvas_ht ;
553
554 // CANVAS
555 canvas_wd = fb_width * 0.91346f ;
556 canvas_ht = fb_height ;
557
558 // MTX
559 MTXOrtho( proj_mtx, canvas_ht * -0.5f,canvas_ht * 0.5f, canvas_wd * -0.5f,canvas_wd * 0.5f, -10.0f,10.0f ) ;
560 GXSetProjection( proj_mtx, GX_ORTHOGRAPHIC ) ;
561
562 MTXIdentity( view_mtx ) ;
563 GXLoadPosMtxImm( view_mtx, GX_PNMTX0 ) ;
564 GXSetCurrentMtx( GX_PNMTX0 ) ;
565
566 // VERTEX
567 GXClearVtxDesc() ;
568 GXSetVtxDesc( GX_VA_POS, GX_DIRECT ) ;
569 GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_F32, 0 ) ;
570
571 // CHANNEL
572 GXSetNumChans( 1 ) ;
573 GXSetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ) ;
574 GXSetChanCtrl( GX_COLOR1A1, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ) ;
575
576 // TEXTURE
577 GXSetNumTexGens( 0 ) ;
578
579 // TEV
580 GXSetNumTevStages( 1 ) ;
581 GXSetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL ) ;
582 GXSetTevColorIn( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0 ) ;
583 GXSetTevColorOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV ) ;
584 GXSetTevAlphaIn( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0 ) ;
585 GXSetTevAlphaOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV ) ;
586 GXSetAlphaCompare( GX_ALWAYS,0, GX_AOP_OR, GX_ALWAYS,0 ) ;
587
588 // SCREEN
589 GXSetBlendMode( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP ) ;
590 GXSetAlphaUpdate( GX_DISABLE ) ;
591
592 GXSetZMode( GX_DISABLE, GX_ALWAYS, GX_DISABLE ) ;
593 GXSetCullMode( GX_CULL_BACK ) ;
594 }
595
596 /*******************************************************************************
597 Draw object
598 *******************************************************************************/
draw_line(f32 x1,f32 y1,f32 x2,f32 y2,GXColor clr,f32 width)599 static void draw_line( f32 x1, f32 y1, f32 x2, f32 y2, GXColor clr, f32 width )
600 {
601 GXSetTevColor( GX_TEVREG0, clr ) ;
602 GXSetLineWidth( (u8)(s32)(width * 6.0f + 0.5f), GX_TO_ZERO ) ;
603
604 GXBegin( GX_LINES, GX_VTXFMT0, 2 ) ;
605 GXPosition2f32( x1, y1 ) ;
606 GXPosition2f32( x2, y2 ) ;
607 GXEnd() ;
608 }
609
draw_box(f32 x1,f32 y1,f32 x2,f32 y2,GXColor clr,f32 width)610 static void draw_box( f32 x1, f32 y1, f32 x2, f32 y2, GXColor clr, f32 width )
611 {
612 draw_line(x1, y1, x2, y1, clr, width);
613 draw_line(x2, y1, x2, y2, clr, width);
614 draw_line(x2, y2, x1, y2, clr, width);
615 draw_line(x1, y2, x1, y1, clr, width);
616 }
617
618