Previous 15994 Revisions Next

r9565 Saturday 13th November, 2010 at 14:34:53 UTC by Fabio Priuli
[SMS] Improved Light Phaser emulation and converted it to use timers that control when
the beam will reach the aim area, to run the function that checks if the pixel displayed is
bright enough to activate the sensor. Details are written in source code. [Enik Land]
[src/mess/drivers]sms.c
[src/mess/includes]sms.h
[src/mess/machine]sms.c
[src/mess/video]smsvdp.c smsvdp.h

src/mess/machine/sms.c
r9564r9565
1919
2020#define MAX_CARTRIDGES        16
2121
22#define LGUN_RADIUS           6
23#define LGUN_X_INTERVAL       4
24
2225typedef struct _sms_driver_data sms_driver_data;
2326struct _sms_driver_data {
2427   UINT8 bios_page_count;
r9564r9565
7477   UINT8 sports_pad_2_y;
7578
7679   /* Data needed for Light Phaser */
77   UINT16 lphaser_latch;
80   emu_timer *lphaser_1_timer;
81   emu_timer *lphaser_2_timer;
82   UINT8 lphaser_1_latch;
83   UINT8 lphaser_2_latch;
7884   int lphaser_x_offs;   /* Needed to 'calibrate' lphaser; set at cart loading */
7985
8086   /* Data needed for SegaScope (3D glasses) */
r9564r9565
161167   }
162168}
163169
164static TIMER_CALLBACK( lightgun_tick )
170/* FIXME: this function is a hack for Light Phaser emulation. Theoretically
171   sms_vdp_hcount_latch() should be used instead, but it returns incorrect
172   position for unknown reason (timing?) */
173static void sms_vdp_hcount_lphaser( running_machine *machine, int hpos )
165174{
166   /* is there a Light Phaser in P1 port? */
167   if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0x0f) == 0x01)
168      crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_ALL);   /* enable crosshair */
169   else
170      crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_NONE);  /* disable crosshair */
171
172   /* is there a Light Phaser in P2 port? */
173   if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0xf0) == 0x10)
174      crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_ALL);   /* enable crosshair */
175   else
176      crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_NONE);  /* disable crosshair */
175    running_device *smsvdp = machine->device("sms_vdp");
176   int hpos_tmp = hpos + sms_state.lphaser_x_offs;
177   UINT8 tmp = ((hpos_tmp - 46) >> 1) & 0xff;
178   
179   //printf ("sms_vdp_hcount_lphaser: hpos %3d hpos_tmp %3d => hcount %2X\n", hpos, hpos_tmp, tmp);
180    sms_vdp_hcount_latch_w(smsvdp, 0, tmp);
177181}
178182
179183
180/* FIXME: this function is a hack. For Light Phaser X pos sms_vdp_hcount_latch
181 function should be used instead, but emulation (timming?) needs to improve. */
182static void sms_vdp_hcount_lphaser( running_machine *machine, int hpos)
184/*
185    Light Phaser (light gun) emulation notes:
186    - The sensor is activated based on color brightness of some individual
187      pixels being drawn by the beam, at circular area where the gun is aiming.
188    - Currently, brightness is calculated based only on single pixels.
189    - In general, after the trigger is pressed, games draw the next frame using
190      a light color pattern, to make sure sensor will be activated. If emulation
191      skips that frame, sensor may stay deactivated. Frameskip set to 0 (no skip) is
192      recommended to avoid problems.
193    - When sensor switches from off to on, a value is latched for HCount register
194      and a flag is set. The emulation uses the flag to avoid sequential latches and
195      to signal that TH line is activated when the status of the input port is read.
196      When the status is read, the flag is cleared, or else it is cleared later when
197      the Pause status is read (end of a frame). This is necessary because the
198      "Color & Switch Test" ROM only reads the TH bit after the VINT line.
199    - The gun test of "Color & Switch Test" is an example that requires checks
200      of sensor status independent of other events, like trigger press or TH bit
201      reads. Another example is the title screen of "Hang-On & Safari Hunt", where
202      the game only reads HCount register in a loop, expecting a latch by the gun.
203    - The whole procedure is managed by a timer callback, that always reschedule
204      itself to run in some intervals when the beam is at the circular area.
205*/
206static int lgun_bright_aim_area( running_machine *machine, emu_timer *timer, int lgun_x, int lgun_y )
183207{
184   UINT8 tmp = ((hpos - 46) >> 1) & 0xff;
208   const int r_x_r = LGUN_RADIUS * LGUN_RADIUS;
185209   running_device *smsvdp = machine->device("sms_vdp");
210   screen_device *screen = screen_first(*machine);
211   const rectangle &visarea = screen->visible_area();
212   int beam_x = screen->hpos();
213   int beam_y = screen->vpos();
214   int dx, dy;
215   int result = 0;
216   int pos_changed = 0;
217   double dx_circ;
186218
187   //printf ("sms_vdp_hcount_lphaser: hpos %3d => hcount %2X\n", hpos, tmp);
188   sms_vdp_hcount_latch_w(smsvdp, 0, tmp);
219    while (1)
220   {
221      dy = abs(beam_y - lgun_y);
222
223      if (dy > LGUN_RADIUS || beam_y < visarea.min_y || beam_y > visarea.max_y)
224      {
225         beam_y = lgun_y - LGUN_RADIUS;
226         if (beam_y < visarea.min_y)
227            beam_y = visarea.min_y;
228         dy = abs(beam_y - lgun_y);
229         pos_changed = 1;
230      }
231      /* step 1: r^2 = dx^2 + dy^2 */
232      /* step 2: dx^2 = r^2 - dy^2 */
233      /* step 3: dx = sqrt(r^2 - dy^2) */
234      dx_circ = ceil(sqrt(r_x_r - (dy * dy)));
235      dx = abs(beam_x - lgun_x);
236
237      if (dx > dx_circ || beam_x < visarea.min_x || beam_x > visarea.max_x)
238      {
239         if (beam_x > lgun_x)
240         {
241            beam_x = 0;
242            beam_y++;
243            continue;
244         }
245         beam_x = lgun_x - dx_circ;
246         if (beam_x < visarea.min_x)
247            beam_x = visarea.min_x;
248         pos_changed = 1;
249      }
250
251      if (!pos_changed)
252      {
253         result = sms_vdp_check_brightness(smsvdp, beam_x, beam_y);
254         /* next check at same line */
255         beam_x += LGUN_X_INTERVAL;
256         pos_changed = 1;
257      }
258      else
259         break;
260   }
261   timer_adjust_oneshot(timer, screen_first(*machine)->time_until_pos(beam_y, beam_x), 0);
262
263   return result;
189264}
190265
191
192266static UINT8 sms_vdp_hcount( running_machine *machine )
193267{
194268   UINT8 tmp;
r9564r9565
244318}
245319
246320
247static int lphaser_sensor_is_on( running_machine *machine, const char *tag_x, const char *tag_y )
321static void lphaser1_sensor_check( running_machine *machine )
248322{
249   int x = screen_hpos_nonscaled(machine->primary_screen, input_port_read(machine, tag_x));
250   int y = screen_vpos_nonscaled(machine->primary_screen, input_port_read(machine, tag_y));
323   const int x = screen_hpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER0"));
324   const int y = screen_vpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER1"));
251325
252   if (sms_vdp_area_brightness(machine->device("sms_vdp"), x, y, 60, 5) >= 0x7f)
326   if (lgun_bright_aim_area(machine, sms_state.lphaser_1_timer, x, y))
253327   {
254      /* avoid latching hcount more than once in a line */
255      if (sms_state.lphaser_latch == 0)
328      if (sms_state.lphaser_1_latch == 0)
256329      {
257         sms_state.lphaser_latch = 1;
258         //sms_vdp_hcount_latch(machine);
259         /* FIXME: sms_vdp_hcount_lphaser() is a hack necessary while normal latch gives too inconstant shot positions */
260         x += sms_state.lphaser_x_offs;
330         sms_state.lphaser_1_latch = 1;
261331         sms_vdp_hcount_lphaser(machine, x);
262332      }
263      return 1;
264333   }
334}
265335
266   sms_state.lphaser_latch = 0;
267   return 0;
336static void lphaser2_sensor_check( running_machine *machine )
337{
338   const int x = screen_hpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER2"));
339   const int y = screen_vpos_nonscaled(screen_first(*machine), input_port_read(machine, "LPHASER3"));
340   
341   if (lgun_bright_aim_area(machine, sms_state.lphaser_2_timer, x, y))
342   {
343      if (sms_state.lphaser_2_latch == 0)
344      {
345         sms_state.lphaser_2_latch = 1;
346         sms_vdp_hcount_lphaser(machine, x);
347      }
348   }
268349}
269350
270351
352// at each frame we check if lightguns are enabled in one of the ports:
353// if so, we turn on crosshair and the lightgun timer
354static TIMER_CALLBACK( lightgun_tick )
355{
356   if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0x0f) == 0x01)
357   {
358      /* enable crosshair */
359      crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_ALL);
360      if (!timer_enabled(sms_state.lphaser_1_timer))
361         lphaser1_sensor_check(machine);
362   }
363   else
364   {
365      /* disable crosshair */
366      crosshair_set_screen(machine, 0, CROSSHAIR_SCREEN_NONE);
367      timer_enable(sms_state.lphaser_1_timer, 0);
368   }
369   
370   if ((input_port_read_safe(machine, "CTRLSEL", 0x00) & 0xf0) == 0x10)
371   {
372      /* enable crosshair */
373      crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_ALL);
374      if (!timer_enabled(sms_state.lphaser_2_timer))
375         lphaser2_sensor_check(machine);
376   }
377   else
378   {
379      /* disable crosshair */
380      crosshair_set_screen(machine, 1, CROSSHAIR_SCREEN_NONE);
381      timer_enable(sms_state.lphaser_2_timer, 0);
382   }
383}
384
385
386static TIMER_CALLBACK( lphaser_1_callback )
387{
388   lphaser1_sensor_check(machine);
389}
390
391
392static TIMER_CALLBACK( lphaser_2_callback )
393{
394   lphaser2_sensor_check(machine);
395}
396
397
398INPUT_CHANGED( lgun1_changed )
399{
400   if (!sms_state.lphaser_1_timer ||
401      (input_port_read_safe(field->port->machine, "CTRLSEL", 0x00) & 0x0f) != 0x01)
402      return;
403   
404   if (newval != oldval)
405      lphaser1_sensor_check(field->port->machine);
406}
407
408INPUT_CHANGED( lgun2_changed )
409{
410   if (!sms_state.lphaser_2_timer ||
411      (input_port_read_safe(field->port->machine, "CTRLSEL", 0x00) & 0xf0) != 0x10)
412      return;
413   
414   if (newval != oldval)
415      lphaser2_sensor_check(field->port->machine);
416}
417
418
271419static void sms_get_inputs( address_space *space )
272420{
273421   UINT8 data = 0x00;
r9564r9565
494642   {
495643      sms_state.paused = 0;
496644   }
645
646   /* clear Light Phaser latch flags for next frame */
647   sms_state.lphaser_1_latch = 0;
648   sms_state.lphaser_2_latch = 0;
497649}
498650
499651READ8_HANDLER( sms_input_port_0_r )
r9564r9565
532684   }
533685   else
534686   {
535      if (sms_state.ctrl_reg & 0x02
536          && (input_port_read_safe(space->machine, "CTRLSEL", 0x00) & 0x0f) == 0x01
537          && lphaser_sensor_is_on(space->machine, "LPHASER0", "LPHASER1"))
687      if (sms_state.ctrl_reg & 0x02 && sms_state.lphaser_1_latch)
538688      {
539689         sms_state.input_port1 &= ~0x40;
690         sms_state.lphaser_1_latch = 0;
540691      }
541      if (sms_state.ctrl_reg & 0x08
542          && (input_port_read_safe(space->machine, "CTRLSEL", 0x00) & 0xf0) == 0x10
543          && lphaser_sensor_is_on(space->machine, "LPHASER2", "LPHASER3"))
692         
693      if (sms_state.ctrl_reg & 0x08 && sms_state.lphaser_2_latch)
544694      {
545695         sms_state.input_port1 &= ~0x80;
696         sms_state.lphaser_2_latch = 0;
546697      }
547698   }
548699
r9564r9565
15851736   machine->add_notifier(MACHINE_NOTIFY_EXIT, sms_machine_stop);
15861737   sms_state.rapid_fire_timer = timer_alloc(machine, rapid_fire_callback , NULL);
15871738   timer_adjust_periodic(sms_state.rapid_fire_timer, ATTOTIME_IN_HZ(10), 0, ATTOTIME_IN_HZ(10));
1739
1740   sms_state.lphaser_1_timer = timer_alloc(machine, lphaser_1_callback , NULL);
1741   sms_state.lphaser_2_timer = timer_alloc(machine, lphaser_2_callback , NULL);
1742
15881743   /* Check if lightgun has been chosen as input: if so, enable crosshair */
15891744   timer_set(machine, attotime_zero, NULL, 0, lightgun_tick);
15901745}
r9564r9565
16461801   sms_state.sports_pad_2_x = 0;
16471802   sms_state.sports_pad_2_y = 0;
16481803
1649   sms_state.lphaser_latch = 0;
1804   sms_state.lphaser_1_latch = 0;
1805   sms_state.lphaser_2_latch = 0;
16501806
16511807   sms_state.sscope_state = 0;
16521808
src/mess/video/smsvdp.c
r9564r9565
13411341}
13421342
13431343
1344static UINT8 color_brightness(rgb_t color)
1344// This is only used by Light Phaser. Should it be moved elsewhere?
1345int sms_vdp_check_brightness(running_device *device, int x, int y)
13451346{
1346   /* reference: http://www.w3.org/TR/AERT#color-contrast */
1347   UINT8 brightness = (RGB_RED(color) * 0.299) + (RGB_GREEN(color) * 0.587) + (RGB_BLUE(color) * 0.114);
1348   //printf ("color brightness: %2X\n", brightness);
1349   return brightness;
1350}
1347   /* brightness of the lightgray color in the frame drawn by Light Phaser games */
1348   const UINT8 sensor_min_brightness = 0x7f;
13511349
1352
1353UINT8 sms_vdp_area_brightness(running_device *device, int x, int y, int x_range, int y_range)
1354{
1355   int beam_x, beam_y;
1356   int dx, dy;
1357   int vint_line;
1358   smsvdp_t *smsvdp;
1359
1350   /* TODO: Check how Light Phaser behaves for border areas. For Gangster Town, should */
1351   /* a shot at right border (HC~=0x90) really appear at active scr, near to left border? */
13601352   if (x < LBORDER_START + LBORDER_X_PIXELS || x >= LBORDER_START + LBORDER_X_PIXELS + 256)
1361   {
1362      return 0x00;
1363   }
1353      return 0;
1354   
1355   smsvdp_t *smsvdp = get_safe_token(device);
1356   rgb_t color = *BITMAP_ADDR32(smsvdp->tmpbitmap, y, x);
13641357
1365   smsvdp = get_safe_token(device);
1358   /* reference: http://www.w3.org/TR/AERT#color-contrast */
1359   UINT8 brightness = (RGB_RED(color) * 0.299) + (RGB_GREEN(color) * 0.587) + (RGB_BLUE(color) * 0.114);
1360   //printf ("color brightness: %2X for x %d y %d\n", brightness, x, y);
13661361
1367   beam_x = device->machine->primary_screen->hpos();
1368   beam_y = device->machine->primary_screen->vpos();
1369   dx = x - beam_x;
1370   dy = y - beam_y;
1371
1372   if (abs(dy) <= y_range && abs(dx) <= x_range)
1373   {
1374      rgb_t color = *BITMAP_ADDR32(smsvdp->tmpbitmap, beam_y, beam_x);
1375      return color_brightness(color);
1376   }
1377
1378   /* Color & Switch Test ROM check TH after the line where VINT    */
1379   /* occurs (237 for NTSC/192 mode). Until details are not figured */
1380   /* out, calculate VINT line and use it for this special case.    */
1381   vint_line = smsvdp->sms_frame_timing[VERTICAL_BLANKING] + smsvdp->sms_frame_timing[TOP_BLANKING]
1382             + smsvdp->sms_frame_timing[TOP_BORDER] + smsvdp->sms_frame_timing[ACTIVE_DISPLAY_V]
1383             + 1;
1384
1385   if (beam_y == vint_line + 1 && abs(dx) <= x_range)
1386   {
1387      rgb_t color = *BITMAP_ADDR32(smsvdp->tmpbitmap, y, x);
1388      return color_brightness(color);
1389   }
1390
1391   return 0x00;
1362   return (brightness >= sensor_min_brightness) ? 1 : 0;
13921363}
13931364
13941365
src/mess/video/smsvdp.h
r9564r9565
8181READ8_DEVICE_HANDLER( sms_vdp_ctrl_r );
8282WRITE8_DEVICE_HANDLER( sms_vdp_ctrl_w );
8383void sms_vdp_set_ggsmsmode( running_device *device, int mode );
84UINT8 sms_vdp_area_brightness( running_device *device, int x, int y, int x_range, int y_range );
84int sms_vdp_check_brightness( running_device *device, int x, int y );
8585
8686#endif /* __SMSVDP_H__ */
src/mess/includes/sms.h
r9564r9565
3636READ8_HANDLER( gg_sio_r );
3737READ8_HANDLER( gg_input_port_2_r );
3838
39INPUT_CHANGED( lgun1_changed );
40INPUT_CHANGED( lgun2_changed );
41
3942void sms_pause_callback( running_machine *machine );
4043void sms_store_int_callback( running_machine *machine, int state );
4144
src/mess/drivers/sms.c
r9564r9565
181181   PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_START ) PORT_NAME(DEF_STR(Pause))
182182
183183   PORT_START("LPHASER0")   /* Light phaser X - player 1 */
184   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR( X, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(11) PORT_PLAYER(1)
184   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR( X, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(11) PORT_PLAYER(1) PORT_CHANGED(lgun1_changed, NULL)
185185
186186   PORT_START("LPHASER1")   /* Light phaser Y - player 1 */
187   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR( Y, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(11) PORT_PLAYER(1)
187   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR( Y, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(11) PORT_PLAYER(1) PORT_CHANGED(lgun1_changed, NULL)
188188
189189   PORT_START("LPHASER2")   /* Light phaser X - player 2 */
190   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR( X, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(21) PORT_PLAYER(2)
190   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR( X, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(15) PORT_CATEGORY(21) PORT_PLAYER(2) PORT_CHANGED(lgun2_changed, NULL)
191191
192192   PORT_START("LPHASER3")   /* Light phaser Y - player 2 */
193   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR( Y, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(25) PORT_CATEGORY(21) PORT_PLAYER(2)
193   PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR( Y, 1.0, 0.0, 0 ) PORT_SENSITIVITY(50) PORT_KEYDELTA(25) PORT_CATEGORY(21) PORT_PLAYER(2) PORT_CHANGED(lgun2_changed, NULL)
194194
195195   PORT_START("RFU")   /* Rapid Fire Unit */
196196   PORT_CONFNAME( 0x03, 0x00, "Rapid Fire Unit - Player 1" )

Previous 15994 Revisions Next


© 1998-2018 The MESS Team