a8d7ff76df
build / build (push) Successful in 2m41s
g3_bitmap_common() (Bitmap.c) used the same bm_w/bm_h for both the on-screen quad size and its texture-coordinate bounds. The former should use patch 04's native-size override for a modded/upscaled bitmap; the latter must always use the bitmap's own real dimensions, since that's still what's actually being sampled. OpenGL never hit this (opengl_bitmap() ignores these bounds and always maps the whole real texture), but the software renderer's h_map() takes them as literal pixel coordinates to interpolate, so the override capped how much of a larger bitmap it ever read - cropping anything past the original sprite's bounds, such as an upscaled critter's limbs. Split into a separate tex_w/tex_h pair, always the bitmap's real size, used for every texture-coordinate computation. README.md: document this, and the previously-undocumented engine-debug-tools RSD8 decode tracing patch (02).
313 lines
13 KiB
Diff
313 lines
13 KiB
Diff
--- 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));
|
|
@@ -176,6 +187,7 @@
|
|
grs_vertex *tempG1, *tempG2;
|
|
fix tempResult;
|
|
int32_t bm_w, bm_h;
|
|
+ int32_t tex_w, tex_h;
|
|
fix dx, dy;
|
|
|
|
// MLA- these were globals, I made them locals for PPC speed, they aren't
|
|
@@ -185,6 +197,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 +230,23 @@
|
|
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, for the on-screen quad's world-space size
|
|
+ // only. tex_w/tex_h stay the bitmap's own real dimensions always: the
|
|
+ // quad's u/v bounds (below) are pixel coordinates into bm's own actual
|
|
+ // pixel data - the software renderer's scanline texture mapper
|
|
+ // (h_map(), gentm.c) interpolates and samples them literally, so
|
|
+ // overriding them to a smaller size here would make it only ever read
|
|
+ // part of a bitmap bigger than that override (such as an upscaled mod's
|
|
+ // replacement art), cropping the rest. OpenGL doesn't read u/v from here
|
|
+ // at all (opengl_bitmap() always maps the whole real texture over the
|
|
+ // quad), which is why this only ever showed up in the software
|
|
+ // renderer.
|
|
+ bm_w = w_override ? w_override : bm->w;
|
|
+ bm_h = h_override ? h_override : bm->h;
|
|
+ tex_w = bm->w;
|
|
+ tex_h = bm->h;
|
|
|
|
tempL = p->gZ; // mov eax,[edi].z
|
|
tempL <<= 8; // sal eax,8 ;should be
|
|
@@ -299,7 +334,7 @@
|
|
tempG1->y = tempL2;
|
|
|
|
// vpl[1][2] = bitmap.w-1
|
|
- tempG1->u = (bm_w - 1) << 16;
|
|
+ tempG1->u = (tex_w - 1) << 16;
|
|
|
|
// vpl[1][3] = 0
|
|
tempG1->v = 0;
|
|
@@ -326,10 +361,10 @@
|
|
tempG1->y = tempL2;
|
|
|
|
// vpl[2][2] = bitmap.w-1
|
|
- tempG1->u = (bm_w - 1) << 16;
|
|
+ tempG1->u = (tex_w - 1) << 16;
|
|
|
|
// vpl[2][3] = bitmap.h-1
|
|
- tempG1->v = (bm_h - 1) << 16;
|
|
+ tempG1->v = (tex_h - 1) << 16;
|
|
|
|
// vpl[2][4] undefined
|
|
|
|
@@ -356,7 +391,7 @@
|
|
tempG1->u = 0;
|
|
|
|
// vpl[3][3] = bitmap.h
|
|
- tempG1->v = bm_h << 16;
|
|
+ tempG1->v = tex_h << 16;
|
|
|
|
// vpl[3][4] undefined
|
|
|
|
@@ -380,11 +415,13 @@
|
|
if (dy > dx)
|
|
dx = dy; // get max value in dx
|
|
|
|
- // shift down because bm_w isn't fixed, and we only double when twice the size
|
|
+ // shift down because tex_w isn't fixed, and we only double when twice the size
|
|
dx >>= 0x11;
|
|
|
|
// make sure doubled bitmap won't overflow unpack buffer (that's roughly 1/5 of 64k)
|
|
- if ((bm_w <= dx) && (bm_w * bm_h <= 12000)) {
|
|
+ // - against the bitmap's own real size (tex_w/tex_h), same as the u/v this doubles
|
|
+ // just below: both operate on bm's actual pixel data, not the on-screen override.
|
|
+ if ((tex_w <= dx) && (tex_w * tex_h <= 12000)) {
|
|
// first fix all u's and v's
|
|
for (tempL = 0; tempL < 4; tempL++) {
|
|
_g3d_bitmap_poly[tempL]->u <<= 1;
|
|
--- 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);
|