diff options
author | 2025-04-11 12:48:28 +0100 | |
---|---|---|
committer | 2025-04-11 07:48:28 -0400 | |
commit | 14644cb8459e8e695334d17b2cfcfcda63342bac (patch) | |
tree | 2763442ce632a376a9a791f86bf554135e837d1f | |
parent | 899e857ef0fa0ba1129618040df97ce3ed13a772 (diff) |
sega/model2.cpp: improve z-sort and clipping (#13575)
* model2: improve z-sort and clipping
Polygons in later windows are always drawn after polygons from earlier windows, regardless of z value.
Four planes representing the visible frustum are now used for polygon clipping; it is no longer necessary to add 1.0 to z during projection.
* model2: only reset the triangle z value at the start of the frame
Zero Gunner uses z value from previous object for enemy bullets; resetting z values leads to them not being visible
Setting master z-clip value to 0xFF disables z-clipping according to Model 2B-CRX manual
-rw-r--r-- | src/mame/sega/model2.cpp | 7 | ||||
-rw-r--r-- | src/mame/sega/model2.h | 3 | ||||
-rw-r--r-- | src/mame/sega/model2_v.cpp | 157 |
3 files changed, 106 insertions, 61 deletions
diff --git a/src/mame/sega/model2.cpp b/src/mame/sega/model2.cpp index 9ede74c40d9..382d55331d2 100644 --- a/src/mame/sega/model2.cpp +++ b/src/mame/sega/model2.cpp @@ -10,15 +10,13 @@ MAME driver by R. Belmont, Olivier Galibert, ElSemi and Angelo Salese. TODO: - - z-sort, focal distance, color gamma and Mip Mapping still needs to be properly sorted in the renderer; + - color gamma and Mip Mapping still needs to be properly sorted in the renderer; - sound comms still needs some work (sometimes m68k doesn't get some commands or play them with a delay); - 2C games needs TGPx4 emulation; - outputs and artwork (for gearbox indicators); - clean-ups; TODO (per-game issues) - - daytona: crashes when coining it up with master network active - culprit is a wrong command parameter in geo_parse texture data opcode; - daytona: car glasses doesn't get loaded during gameplay; - doa, doaa: corrupted sound, eventually becomes silent; - dynamcopc: corrupts palette for 2d (most likely unrelated with the lack of DSP); @@ -31,11 +29,8 @@ - manxtt: no escape from "active motion slider" tutorial (needs analog inputs), bypass it by entering then exiting service mode; - sgt24h: first turn in easy reverse course has ugly rendered mountain in background; - - skytargt: really slow during gameplay (fixed?); - - skytargt: short draw distance (might be down to z-sort); - srallyc: some 3d elements doesn't show up properly (tree models, last hill in course 1 is often black colored); - vcop: sound dies at enter initial screen (i.e. after played the game once) (untested); - - vcop: missing 3d at stage select screen (priority?); - vstriker: stadium ads have terrible colors (they uses the wrong color table, @see video/model2rd.hxx) Notes: diff --git a/src/mame/sega/model2.h b/src/mame/sega/model2.h index 1ef438cf5f6..6f9e9351f63 100644 --- a/src/mame/sega/model2.h +++ b/src/mame/sega/model2.h @@ -768,6 +768,7 @@ struct model2_state::triangle u8 luma = 0; int16_t viewport[4] = { 0, 0, 0, 0 }; int16_t center[2] = { 0, 0 }; + u8 window = 0; }; struct model2_state::quad_m2 @@ -822,6 +823,8 @@ struct model2_state::raster_state u16 max_z = 0; // Maximum sortable Z value u16 texture_ram[0x10000]; // Texture RAM pointer u8 log_ram[0x40000]; // Log RAM pointer + u8 cur_window = 0; // Current window + plane clip_plane[4][4]; // Polygon clipping planes }; /******************************************* diff --git a/src/mame/sega/model2_v.cpp b/src/mame/sega/model2_v.cpp index fdffc686003..ca850ff36b0 100644 --- a/src/mame/sega/model2_v.cpp +++ b/src/mame/sega/model2_v.cpp @@ -42,6 +42,9 @@ - The system allows for Z-Sort override, by means of specifying whether a polygon will use the same Z value ordinal as the previous polygon, or the calculated minimum or maximum from it's points. This allows for a full object to be in front of another, even though the first object might have some z coordinates that are bigger than the second object's z coordinates. + - Polygons from later windows will always be rendered on top of polygons from earlier windows, regardless of z value. This + can be seen in the name entry screen in Sega Rally for example, where letters meant to be behind the car are rendered in an + earlier window and letters in front of the car are rendered in a later window. - The current implementation takes the effective computed z value for the polygon and converts it into a 4.12 fixed point representation, used as an index into an array of linked polygons. Every polygon with the same z value is linked with any previous polygon that had that same z value: @@ -55,17 +58,14 @@ As we add polygons to the array, we also keep track of the minimum and maximum z-value indexes seen this frame. When it's time to render, we start and the max z-value seen, and iterate through the array going down to the minimum z-value seen, clipping and rendering the linked polygons as we go. - Unfortunately, it seems there's something not quite right, as there are some visible Z-Sort problems in various games. - Perhaps the linked list of polygons need to be presorted by a unknown factor before rendering. Clip Notes and Known Bugs: - - The Z-Clip happens at z=0. Since dividing by 0 during projection would give bogus values, we scale by a distance of - 1, thus our projection equation turns into vd.x = (d*vs.x) / (d+vs.z) where d = 1, since we know we won't have polygons - with a z < 0. This, however, poses the problem that for objects originally between 0.0 and 1.0 are not going to be - scaled properly. We still need to find a solution for this. + - Four clipping planes representing the viewing frustum are used to clip polygons prior to projection. In the rare event + that a polygon intersects with the origin exactly, it will produce a clipped vertex at (0.0, 0.0, 0,0) which would result + in NaNs being generated during projection; a tiny value is added to z to prevent this. - A small offset need to be added horizontally and vertically to the viewport and center variables for certain games (like the original Model 2 games). The coordinate system has been worked out from the 2B specifications, but the older games need a slight adjustment. @@ -92,6 +92,7 @@ #include "model2.h" #include <cmath> +#include <limits> #define pz p[0] #define pu p[1] @@ -196,17 +197,11 @@ inline u16 model2_state::float_to_zval( float floatval ) return 0xffff; } -static int32_t clip_polygon(poly_vertex *v, int32_t num_vertices, poly_vertex *vout) +static int32_t clip_polygon(poly_vertex *v, int32_t num_vertices, poly_vertex *vout, model2_state::plane clip_plane) { poly_vertex *cur, *out; float curdot, nextdot, scale; int32_t i, curin, nextin, nextvert, outcount; - model2_state::plane clip_plane; - - clip_plane.normal.x = 0.0f; - clip_plane.normal.y = 0.0f; - clip_plane.normal.pz = 1.0f; - clip_plane.distance = 0.0f; outcount = 0; @@ -265,7 +260,7 @@ inline bool model2_state::check_culling( raster_state *raster, u32 attr, float m return true; /* if the minimum z value is bigger than the master z clip value, don't render */ - if ( (int32_t)(1.0/min_z) > raster->master_z_clip ) + if (raster->master_z_clip != 0xFF && (int32_t)(1.0 / min_z) > raster->master_z_clip) return true; /* if the maximum z value is < 0 then we can safely clip the entire polygon */ @@ -316,6 +311,7 @@ void model2_state::raster_init( memory_region *texture_rom ) void model2_state::model2_3d_zclip_w(u32 data) { + // setting this register to 0xFF disables z-clip m_raster->master_z_clip = data; } @@ -444,10 +440,20 @@ void model2_state::model2_3d_process_quad( raster_state *raster, u32 attr ) if ( cull == false ) { int32_t clipped_verts; - poly_vertex verts[10]; + poly_vertex verts_in[10], verts_out[10]; + + for (int i = 0; i < 4; i++) + verts_in[i] = object.v[i]; + + clipped_verts = 4; - /* do near z clipping */ - clipped_verts = clip_polygon( object.v, 4, verts); + /* do clipping */ + for (int i = 0; i < 4; i++) + { + clipped_verts = clip_polygon(verts_in, clipped_verts, verts_out, raster->clip_plane[raster->center_sel][i]); + for (int j = 0; j < clipped_verts; j++) + verts_in[j] = verts_out[j]; + } if ( clipped_verts > 2 ) { @@ -495,9 +501,12 @@ void model2_state::model2_3d_process_quad( raster_state *raster, u32 attr ) tri->center[0] = raster->center[raster->center_sel][0]; tri->center[1] = raster->center[raster->center_sel][1]; - memcpy( &tri->v[0], &verts[0], sizeof( poly_vertex ) ); - memcpy( &tri->v[1], &verts[i-1], sizeof( poly_vertex ) ); - memcpy( &tri->v[2], &verts[i], sizeof( poly_vertex ) ); + /* set the window */ + tri->window = raster->cur_window; + + memcpy( &tri->v[0], &verts_out[0], sizeof( poly_vertex ) ); + memcpy( &tri->v[1], &verts_out[i-1], sizeof( poly_vertex ) ); + memcpy( &tri->v[2], &verts_out[i], sizeof( poly_vertex ) ); /* add to our sorted list */ tri->next = nullptr; @@ -654,14 +663,24 @@ void model2_state::model2_3d_process_triangle( raster_state *raster, u32 attr ) raster->triangle_z = zvalue; - /* if we're not culling, do z-clip and add to out triangle list */ + /* if we're not culling, do clipping and add to out triangle list */ if ( cull == false ) { int32_t clipped_verts; - poly_vertex verts[10]; + poly_vertex verts_in[10], verts_out[10]; - /* do near z clipping */ - clipped_verts = clip_polygon( object.v, 3, verts); + for (int i = 0; i < 3; i++) + verts_in[i] = object.v[i]; + + clipped_verts = 3; + + /* do clipping */ + for (int i = 0; i < 4; i++) + { + clipped_verts = clip_polygon(verts_in, clipped_verts, verts_out, raster->clip_plane[raster->center_sel][i]); + for (int j = 0; j < clipped_verts; j++) + verts_in[j] = verts_out[j]; + } if ( clipped_verts > 2 ) { @@ -709,9 +728,12 @@ void model2_state::model2_3d_process_triangle( raster_state *raster, u32 attr ) tri->center[0] = raster->center[raster->center_sel][0]; tri->center[1] = raster->center[raster->center_sel][1]; - memcpy( &tri->v[0], &verts[0], sizeof( poly_vertex ) ); - memcpy( &tri->v[1], &verts[i-1], sizeof( poly_vertex ) ); - memcpy( &tri->v[2], &verts[i], sizeof( poly_vertex ) ); + /* set the window */ + tri->window = raster->cur_window; + + memcpy( &tri->v[0], &verts_out[0], sizeof( poly_vertex ) ); + memcpy( &tri->v[1], &verts_out[i-1], sizeof( poly_vertex ) ); + memcpy( &tri->v[2], &verts_out[i], sizeof( poly_vertex ) ); /* add to our sorted list */ tri->next = nullptr; @@ -796,13 +818,13 @@ void model2_renderer::model2_3d_render(triangle *tri, const rectangle &cliprect) extra.texmirrory = 0;//(tri->texheader[0] >> 8) & 1; extra.texsheet = (tri->texheader[2] & 0x1000) ? m_state.m_textureram1 : m_state.m_textureram0; - tri->v[0].pz = 1.0f / (1.0f + tri->v[0].pz); + tri->v[0].pz = 1.0f / (tri->v[0].pz + std::numeric_limits<float>::min()); tri->v[0].pu = tri->v[0].pu * tri->v[0].pz * (1.0f / 8.0f); tri->v[0].pv = tri->v[0].pv * tri->v[0].pz * (1.0f / 8.0f); - tri->v[1].pz = 1.0f / (1.0f + tri->v[1].pz); + tri->v[1].pz = 1.0f / (tri->v[1].pz + std::numeric_limits<float>::min()); tri->v[1].pu = tri->v[1].pu * tri->v[1].pz * (1.0f / 8.0f); tri->v[1].pv = tri->v[1].pv * tri->v[1].pz * (1.0f / 8.0f); - tri->v[2].pz = 1.0f / (1.0f + tri->v[2].pz); + tri->v[2].pz = 1.0f / (tri->v[2].pz + std::numeric_limits<float>::min()); tri->v[2].pu = tri->v[2].pu * tri->v[2].pz * (1.0f / 8.0f); tri->v[2].pv = tri->v[2].pv * tri->v[2].pz * (1.0f / 8.0f); @@ -878,8 +900,8 @@ inline void model2_state::model2_3d_project( triangle *tri ) for( i = 0; i < 3; i++ ) { /* project the vertices */ - tri->v[i].x = m_crtc_xoffset + tri->center[0] + (tri->v[i].x / (1.0f+tri->v[i].pz)); - tri->v[i].y = ((384 - tri->center[1])+m_crtc_yoffset) - (tri->v[i].y / (1.0f+tri->v[i].pz)); + tri->v[i].x = m_crtc_xoffset + tri->center[0] + (tri->v[i].x / (tri->v[i].pz + std::numeric_limits<float>::min())); + tri->v[i].y = ((384 - tri->center[1])+m_crtc_yoffset) - (tri->v[i].y / (tri->v[i].pz + std::numeric_limits<float>::min())); } } @@ -897,6 +919,13 @@ void model2_state::model2_3d_frame_start( void ) /* reset the min-max sortable Z values */ raster->min_z = 0xFFFF; raster->max_z = 0; + + /* reset the triangle z value */ + // Zero Gunner sets backgrounds with "previous z value" mode at the start of the display list, + // needs this to be this big in order to work properly + raster->triangle_z = 1e10; + + raster->cur_window = 0; } void model2_state::model2_3d_frame_end( bitmap_rgb32 &bitmap, const rectangle &cliprect ) @@ -910,23 +939,29 @@ void model2_state::model2_3d_frame_end( bitmap_rgb32 &bitmap, const rectangle &c m_poly->destmap().fill(0x00000000, cliprect); - /* go through the Z levels, and render each bucket */ - for( z = raster->max_z; z >= raster->min_z; z-- ) + for (u8 window = 0; window <= raster->cur_window; window++) { - /* see if we have items at this z level */ - if ( raster->tri_sorted_list[z] != nullptr ) + /* go through the Z levels, and render each bucket */ + for ( z = raster->max_z; z >= raster->min_z; z-- ) { - /* get a pointer to the first triangle */ - triangle *tri = raster->tri_sorted_list[z]; - - /* and loop clipping and rendering each triangle */ - while( tri != nullptr ) + /* see if we have items at this z level */ + if ( raster->tri_sorted_list[z] != nullptr ) { - /* project and render */ - model2_3d_project( tri ); - m_poly->model2_3d_render(tri, cliprect); + /* get a pointer to the first triangle */ + triangle *tri = raster->tri_sorted_list[z]; + + /* and loop clipping and rendering each triangle */ + while( tri != nullptr ) + { + if (tri->window == window) + { + /* project and render */ + model2_3d_project( tri ); + m_poly->model2_3d_render(tri, cliprect); + } - tri = (triangle *)tri->next; + tri = (triangle *)tri->next; + } } } } @@ -1067,6 +1102,26 @@ void model2_state::model2_3d_push( raster_state *raster, u32 input ) if ( raster->center[i][1] & 0x800 ) raster->center[i][1] = -( 0x800 - (raster->center[i][1] & 0x7FF) ); + + // calculate left clipping plane + raster->clip_plane[i][0].normal.x = 1.0f / float(raster->center[i][0] - raster->viewport[0]); + raster->clip_plane[i][0].normal.y = 0.0f; + raster->clip_plane[i][0].normal.pz = 1.0f; + + // calculate right clipping plane + raster->clip_plane[i][1].normal.x = 1.0f / float(raster->center[i][0] - raster->viewport[2]); + raster->clip_plane[i][1].normal.y = 0.0f; + raster->clip_plane[i][1].normal.pz = 1.0f; + + // calculate top clipping plane + raster->clip_plane[i][2].normal.x = 0.0f; + raster->clip_plane[i][2].normal.y = 1.0f / float(raster->center[i][1] - raster->viewport[3]); + raster->clip_plane[i][2].normal.pz = 1.0f; + + // calculate bottom clipping plane + raster->clip_plane[i][3].normal.x = 0.0f; + raster->clip_plane[i][3].normal.y = 1.0f / float(raster->center[i][1] - raster->viewport[1]); + raster->clip_plane[i][3].normal.pz = 1.0f; } /* done with this command */ @@ -1140,11 +1195,6 @@ void model2_state::model2_3d_push( raster_state *raster, u32 input ) /* extract center select */ raster->center_sel = ( input >> 6 ) & 3; - - /* reset the triangle z value */ - // Zero Gunner sets backgrounds with "previous z value" mode at the start of the display list, - // needs this to be this big in order to work properly - raster->triangle_z = 1e10; } } } @@ -2018,6 +2068,8 @@ u32 *model2_state::geo_window_data( geo_state *geo, u32 opcode, u32 *input ) /* start by pushing the opcode */ model2_3d_push( raster, opcode >> 23 ); + raster->cur_window++; + /* we're going to move 6 coordinates to the 3d rasterizer: - starting coordinate @@ -2526,11 +2578,6 @@ void model2_state::geo_parse( void ) /* if it's a jump opcode, do the jump */ if ( opcode & 0x80000000 ) { - // TODO: daytona with master network enabled hardlocks by trying a jump with 0xffff0080 as opcode - // bad timings for geo_parse? - if(opcode & 0x078000000 ) - return; - /* get the address */ address = (opcode & 0x1FFFF) / 4; |