--- a/src/GameSrc/objsim.c +++ b/src/GameSrc/objsim.c @@ -1867,6 +1867,63 @@ return (OK); } +// Every critter view's base-game bitmap size, keyed by resource ID and +// frame index within that animation - filled in by +// cache_critter_native_size() below while objart2.res/objart3.res's +// base-game data is still the active resolver for these IDs (before +// LoadModFiles() hands priority to a mod again), read back by +// get_critter_native_size() in gameobj.c. A mod that replaces a critter's +// art with a different-resolution bitmap changes how much detail is visible, +// not the object's on-screen size: g3_bitmap_common() (Bitmap.c) uses this +// cached size instead of the (possibly modded, possibly larger or smaller) +// bitmap's own width/height when computing the sprite's world-space extent. +// Keyed per frame, not just per view: an animation's bounding box commonly +// changes size between poses, same as the base game's own art does, so a +// single per-view size doesn't fit every frame of it - using one here left +// every frame but the first using the wrong override, visibly corrupting +// the sprite's UV mapping (g3_bitmap_common() uses this same size for +// texture coordinates, not just world-space extent) once a mod's per-frame +// sizes diverge from each other. MAX_CRITTER_FRAMES comfortably covers any +// animation in the base game's own data; cache_critter_native_size() clamps +// to it defensively rather than overrunning the array if that ever changes. +// Id is uint16_t, so a flat array indexed by it needs no bounds check. +#define MAX_CRITTER_FRAMES 32 +static short s_critterNativeW[65536][MAX_CRITTER_FRAMES]; +static short s_critterNativeH[65536][MAX_CRITTER_FRAMES]; + +static void cache_critter_native_size(Id id) { + RefTable *prt; + FrameDesc *f; + int numFrames, frame; + if (s_critterNativeW[id][0] != 0) + return; + prt = (RefTable *)ResLock(id); + if (prt == NULL) + return; + numFrames = prt->numRefs; + ResUnlock(id); + if (numFrames > MAX_CRITTER_FRAMES) + numFrames = MAX_CRITTER_FRAMES; + for (frame = 0; frame < numFrames; frame++) { + f = (FrameDesc *)RefLock(MKREF(id, frame)); + if (f == NULL) + continue; + s_critterNativeW[id][frame] = f->bm.w; + s_critterNativeH[id][frame] = f->bm.h; + RefUnlock(MKREF(id, frame)); + } +} + +// w/h are left untouched (so a caller can default to 0, meaning "no cached +// native size, use the bitmap's own dimensions") if id/frame was never +// cached. +void get_critter_native_size(Id id, short frame, short *w, short *h) { + if ((frame < 0) || (frame >= MAX_CRITTER_FRAMES) || (s_critterNativeW[id][frame] == 0)) + return; + *w = s_critterNativeW[id][frame]; + *h = s_critterNativeH[id][frame]; +} + errtype obj_set_secondary_properties() { char i, j; RefTable *prt; @@ -1878,6 +1935,28 @@ // Warning(("Problem opening object art cache!\n")); return (ERR_NOEFFECT); } + + // Snapshot every critter view's base-game bitmap size while it's still + // the active resolver for these IDs, before LoadModFiles() below hands + // priority back to a mod - see cache_critter_native_size() and its own + // comment. Full 8-view postures need every view individually (their IDs + // are 8 consecutive resource IDs, one per view); front-only postures are + // a single ID (matching the frame-count loop below). + for (i = 0; i < NUM_CRITTER; i++) { + if (CritterProps[i].views > 1) { + for (j = 0; j < NUM_CRITTER_POSTURES; j++) { + if (j >= FIRST_FRONT_POSTURE) { + cache_critter_native_size(posture_bases[j] + i); + } else { + int v; + for (v = 0; v < 8; v++) { + cache_critter_native_size(critter_id_table[i] + posture_bases[j] + v); + } + } + } + } + } + // The two opens above re-register every resource ID objart2.res/ // objart3.res contain, reclaiming any of them a mod file already // overrode (see LoadModFiles() in Modding.c) - restore mod priority --- a/src/GameSrc/Headers/objsim.h +++ b/src/GameSrc/Headers/objsim.h @@ -108,6 +108,7 @@ errtype obj_load_properties(); errtype obj_create_player(ObjLoc *plr_loc); Ref ref_from_critter_data(ObjID oid, int triple, byte posture, short frame, short view); +void get_critter_native_size(Id id, short frame, short *w, short *h); errtype obj_zero_unused(); grs_bitmap *obj_get_model_data(ObjID id, fix *x, fix *y, fix *z, grs_bitmap *bm2, Ref *ref1, Ref *ref2); errtype obj_model_hack(ObjID id, uchar *hack_x, uchar *hack_y, uchar *hack_z, uchar *hack_type); --- a/src/GameSrc/gameobj.c +++ b/src/GameSrc/gameobj.c @@ -104,7 +104,7 @@ void draw_ice(void); void draw_ice_wall(void); void _fr_draw_tmtile(grs_bitmap *draw_bm, int col_val, g3s_phandle *plst, uchar dblface, uchar use_lighting); -void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y); +void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y, short native_w, short native_h); int munge_val(int val, int range, int delta) { int base = range - delta; @@ -575,7 +575,7 @@ // note this always has show_obj's p for p and _fdt_dist for dist.. perhaps shouldnt pass them //#pragma disable_message(202) -void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y) { +void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y, short native_w, short native_h) { #ifdef SMOOTH_BITMAPS grs_canvas tmp_can; grs_bitmap tmp_bm; @@ -597,7 +597,7 @@ if ((anch_x <= 0) && (anch_y <= 0)) bitmap_verts = g3_light_bitmap(draw_bm, anchor); else - bitmap_verts = g3_light_anchor_bitmap(draw_bm, anchor, anch_x, anch_y); + bitmap_verts = g3_light_anchor_bitmap(draw_bm, anchor, anch_x, anch_y, native_w, native_h); if (sc) g3_set_bitmap_scale(fix_make(0, (int)(2048 / 3)), fix_make(0, (int)(2048 / 3))); @@ -855,7 +855,7 @@ if ((anchors_3d[o3drep].x > 0) || (anchors_3d[o3drep].y > 0)) _o_rad = 0; _fr_draw_bitmap(bitmaps_3d[o3drep], _fdt_dist, ObjProps[objtrip].flags & MY_IM_LARGE, anchors_3d[o3drep].x, - anchors_3d[o3drep].y); + anchors_3d[o3drep].y, 0, 0); if (global_fullmap->cyber) if (obj_ICE_ICE_BABY(_fr_cobj)) draw_ice(); @@ -995,7 +995,7 @@ trgp = tpdata->bits + tele_bm.h * tele_bm.w; LG_memcpy(dstp, srcp, trgp - srcp); - _fr_draw_bitmap(&tele_bm, _fdt_dist, FALSE, anch.ul.x, anch.ul.y); + _fr_draw_bitmap(&tele_bm, _fdt_dist, FALSE, anch.ul.x, anch.ul.y, 0, 0); release_critter_bitmap_fast(ref); return; } @@ -1016,11 +1016,15 @@ #endif break; } - _fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y); + { + short native_w = 0, native_h = 0; + get_critter_native_size(REFID(ref), REFINDEX(ref), &native_w, &native_h); + _fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y, native_w, native_h); + } release_critter_bitmap_fast(ref); } else { tpdata = bitmaps_3d[o3drep + view]; - _fr_draw_bitmap(tpdata, _fdt_dist, 0, -1, -1); + _fr_draw_bitmap(tpdata, _fdt_dist, 0, -1, -1, 0, 0); } } break; --- a/src/Libraries/3D/Source/Bitmap.c +++ b/src/Libraries/3D/Source/Bitmap.c @@ -88,6 +88,14 @@ // fix _g3d_bitmap_y_iscale = 0x010000; long _g3d_bitmap_u_anchor = 0; long _g3d_bitmap_v_anchor = 0; +// Non-zero overrides the bitmap's own w/h in g3_bitmap_common()'s world-space +// size computation below, decoupling a bitmap's on-screen size from its +// actual pixel dimensions - set by g3_light_anchor_bitmap(), consumed and +// reset back to 0 by g3_bitmap_common() so it never leaks into an unrelated +// bitmap drawn via a different entry point (g3_bitmap()/g3_light_bitmap()/ +// do_bitmap() never set these). +short _g3d_bitmap_w_override = 0; +short _g3d_bitmap_h_override = 0; fix _g3d_roll_matrix[6]; uchar *_g3d_bitmap_clut; int _g3d_light_flag; @@ -132,10 +140,13 @@ return (g3_bitmap_common(bm, (g3s_phandle)p)); } -grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor) { +grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor, short w_override, + short h_override) { _g3d_light_flag = 2; _g3d_bitmap_u_anchor = u_anchor; _g3d_bitmap_v_anchor = v_anchor; + _g3d_bitmap_w_override = w_override; + _g3d_bitmap_h_override = h_override; _g3d_bitmap_clut = (p->i & 0x00ff00) + grd_screen->ltab; _g3d_bitmap_poly = vpl; return (g3_bitmap_common(bm, p)); @@ -185,6 +196,15 @@ long rm2; long rm3; + // Read and reset right at entry, before any early return below - a + // pending override must never survive to affect a later, unrelated + // bitmap draw that doesn't set it itself (see _g3d_bitmap_w_override's + // own comment). + short w_override = _g3d_bitmap_w_override; + short h_override = _g3d_bitmap_h_override; + _g3d_bitmap_w_override = 0; + _g3d_bitmap_h_override = 0; + #ifdef stereo_on if (_g3d_stereo & 1) { @@ -209,9 +229,11 @@ if ((p->codes & CC_CLIP_OVERFLOW) != 0) return (0L); - // copy a few things into locals - bm_w = bm->w; - bm_h = bm->h; + // copy a few things into locals - w_override/h_override, if set (see + // _g3d_bitmap_w_override's own comment), take the place of the bitmap's + // own pixel dimensions here. + bm_w = w_override ? w_override : bm->w; + bm_h = h_override ? h_override : bm->h; tempL = p->gZ; // mov eax,[edi].z tempL <<= 8; // sal eax,8 ;should be --- a/src/Libraries/3D/Source/3d.h +++ b/src/Libraries/3D/Source/3d.h @@ -720,7 +720,8 @@ grs_vertex **g3_light_bitmap(grs_bitmap *bm, g3s_phandle p); // MLA #pragma aux g3_light_bitmap "*" parm [esi] [edi] modify [eax ebx ecx // edx]; -grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor); +grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor, short w_override, + short h_override); // MLA #pragma aux g3_light_anchor_bitmap "*" parm [esi] [edi] [eax] [edx] // modify [eax ebx ecx edx]; grs_vertex **g3_full_light_bitmap(grs_bitmap *bm, grs_vertex **p);