MP: world structures (garages/walls) TARGETABLE -- boresight ray-tests the static collision tree (task #50)
The boresight's non-mech pick only sampled the VISUAL heightfield (BTGroundRayHit), which on arena1 is a single flat 'sky'-named ground mesh (and class-42 BuildTables likewise holds only 'sky') -- so shots passed THROUGH the garages/walls and only mechs moved the HUD range axis (user-reported regression: "buildings used to move the range axis; now only mechs do, and you can only fire at a peer"). Real fix: the boresight now ALSO ray-tests the ZONE'S STATIC COLLISION SOLID TREE -- the same geometry that already blocks the mech's walk. Authentic engine mechanism: Mover::FindBoxedSolidHitBy already tests the static world via zone->GetCollisionRoot()->FindBoundingBoxHitBy(line). Factored its "static world" tail into Mover::FindStaticSolidHitBy(Line*) (static solids only, no movers), wrapped by Mech::WorldStructurePick(start,dir,range,&hit) (builds the world-space Line, reads the entry point back via line->FindEnd since HitByBounded clips line->length=enter). Boresight pick order is now: closest MECH (PickRayHit, damage zones + lock) -> closest STRUCTURE (WorldStructurePick; occludes a mech BEHIND it; designates the gBTTerrainEntity sentinel + entry point, so the range caret reads the structure distance and NO lock ring draws, mech4.cpp:4529) -> flat ground (BTGroundRayHit) -> sky (fire-at-nothing). Also un-skips arena1's misnamed-'sky' flat ground so the ground tier works (btvisgnd geometry-aware skip + [mapent]/[rendent] census). Verified headless: a BT_WSWEEP horizontal ray-fan on arena1 tracks position (3/24 hits near the boundary -> 17-21/24 inside the interior garage cluster -> 3/24 past it = DISCRETE interior solids, not an enclosing box), no crash/assert/AV. Interactive aim (BTGetAimRay) can't run headless (no window -> noRay), so the sweep is the headless proof; interactive aim is user-verified. Note: FindBoundingBoxUnder (the ground/containedByNode BoundingBoxTree) is DOWNWARD-only (gravity/ground-snap: *height = FindDistanceBelowBounded), useless for a horizontal boresight; the static SOLID tree's FindBoundingBoxHitBy is the only ray-vs-world query. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ee6e19e86e
commit
de8f6d02c1
@@ -247,6 +247,23 @@ void
|
||||
else if (gBTTerrainEntity == 0)
|
||||
gBTTerrainEntity = entity; // else the first world entity
|
||||
}
|
||||
// CENSUS: what world-geometry entities does the RENDER tree walk?
|
||||
// (the garages/structures may be here even if not in the map's class-42
|
||||
// instance stream that BuildTables reads.)
|
||||
if (getenv("BT_GROUND_LOG"))
|
||||
{
|
||||
Derivation *dv = entity->GetClassDerivations();
|
||||
const int isTer = entity->IsDerivedFrom(*Terrain::GetClassDerivations())?1:0;
|
||||
const Point3D &ep = entity->localOrigin.linearPosition;
|
||||
// tag NON-terrain entities distinctly -- these are the structures
|
||||
// (garages/walls/props); log their world XZ so we can spawn beside
|
||||
// one and confirm it is a collision solid the boresight now hits.
|
||||
DEBUG_STREAM << (isTer ? "[rendent]" : "[rendent-STRUCT]")
|
||||
<< " class='" << (dv && dv->className ? dv->className : "?")
|
||||
<< "' terrain=" << isTer
|
||||
<< " pos=(" << ep.x << "," << ep.y << "," << ep.z << ")"
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
DPLRenderer::MakeEntityRenderables(
|
||||
entity, model_resource, view_type);
|
||||
|
||||
@@ -168,6 +168,14 @@ bool BuildTables()
|
||||
const float *pos = (const float *)(base + p + 48);
|
||||
p += (mlen + 3) & ~3u;
|
||||
|
||||
// CENSUS (BT_GROUND_LOG): log EVERY map entity's class + name, so we can see
|
||||
// what class the garages/structures are (the class-42 filter may drop them).
|
||||
if (getenv("BT_GROUND_LOG"))
|
||||
{
|
||||
ResourceDescription *crd = rf->FindResourceDescription(rid);
|
||||
DEBUG_STREAM << "[mapent] cls=" << cls << " mflags=" << mflags
|
||||
<< " name='" << (crd ? crd->resourceName : "?") << "'\n" << std::flush;
|
||||
}
|
||||
if (mflags & 0x2) continue; // include marker
|
||||
if (cls != 42) continue; // terrain/static cultural only
|
||||
|
||||
|
||||
@@ -539,6 +539,37 @@ Logical
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// World-STRUCTURE boresight pick. Ray-test the zone's STATIC collision solid
|
||||
// tree (the world geometry -- garages, walls, props -- that the mech's walk
|
||||
// already collides against) along the boresight, and hand back the entry point
|
||||
// of the closest structure the ray strikes. This is the authentic non-mech
|
||||
// world pick: Mover::FindStaticSolidHitBy is the "test against the static world"
|
||||
// tail of the engine's own FindBoxedSolidHitBy (MOVER.cpp), which is what the
|
||||
// mover-vs-world collision uses -- so a shot lands on EXACTLY the geometry that
|
||||
// blocks the walk (the user's "structures block the mech, but weapons don't").
|
||||
//
|
||||
// The ray is WORLD-space (the static tree is world-space, unlike a mech's local
|
||||
// collision template). FindBoundingBoxHitBy clips the Line's length to the
|
||||
// entry distance (BoundingBox::HitByBounded -> line->length = enter), so
|
||||
// line->FindEnd yields the world entry point on the struck solid.
|
||||
//
|
||||
Logical
|
||||
Mech::WorldStructurePick(const Point3D &start, const Vector3D &dir,
|
||||
Scalar max_range, Point3D *hit_world)
|
||||
{
|
||||
Scalar dlen = (Scalar)Sqrt(dir.x*dir.x + dir.y*dir.y + dir.z*dir.z);
|
||||
if (dlen < 1e-6f)
|
||||
return False;
|
||||
Line ray(start, UnitVector(dir.x/dlen, dir.y/dlen, dir.z/dlen), max_range);
|
||||
BoxedSolid *solid = FindStaticSolidHitBy(&ray); // static world only
|
||||
if (solid == 0)
|
||||
return False;
|
||||
if (hit_world != 0)
|
||||
ray.FindEnd(hit_world); // clipped length -> entry point
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// The roster torso (sinkSourceSubsystem @0x438, ClassID 0xBC5) -- parity with
|
||||
// the binary's *(mech+0x438).
|
||||
|
||||
@@ -693,6 +693,14 @@ public:
|
||||
Logical PickRayHit(const Point3D &start, const Vector3D &dir,
|
||||
Scalar max_range, Point3D *hit_world);
|
||||
|
||||
// World-STRUCTURE boresight pick: ray-test the zone's STATIC collision solid
|
||||
// tree (garages/walls/props -- the geometry that blocks the walk) along the
|
||||
// boresight and return the entry point of the closest structure hit. The
|
||||
// authentic non-mech world pick (Mover::FindStaticSolidHitBy, the static-world
|
||||
// half of the engine's FindBoxedSolidHitBy). Defined in mech.cpp.
|
||||
Logical WorldStructurePick(const Point3D &start, const Vector3D &dir,
|
||||
Scalar max_range, Point3D *hit_world);
|
||||
|
||||
// Per-weapon beam render walk (task #33; extracted task #51 so it runs for
|
||||
// EVERY mech -- player, dummy masters, and MP replicants whose emitters
|
||||
// carry replicated discharge state). Defined in mech4.cpp.
|
||||
|
||||
@@ -4325,6 +4325,39 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// WORLD-STRUCTURE SWEEP PROBE (BT_WSWEEP, task #50 verification): once a
|
||||
// second cast a horizontal fan of rays from the eyepoint against the
|
||||
// STATIC collision solid tree (WorldStructurePick -> FindStaticSolidHitBy)
|
||||
// and log the hit count + nearest range. Confirms arena1's structures
|
||||
// (garages/walls) ARE in the collision tree the boresight now queries --
|
||||
// independent of the render sentinel or interactive aim. Diag, off by default.
|
||||
if (getenv("BT_WSWEEP"))
|
||||
{
|
||||
static float s_ws = 0.0f; s_ws += dt;
|
||||
if (s_ws >= 1.0f)
|
||||
{
|
||||
s_ws = 0.0f;
|
||||
Point3D eye = localOrigin.linearPosition; eye.y += 30.0f;
|
||||
int hits = 0; float nearest = 1e30f; float ndir = -1.0f;
|
||||
for (int a = 0; a < 24; ++a)
|
||||
{
|
||||
double th = (double)a * (6.2831853071795862 / 24.0);
|
||||
Vector3D d((float)cos(th), 0.0f, (float)sin(th));
|
||||
Point3D hp;
|
||||
if (WorldStructurePick(eye, d, 2000.0f, &hp))
|
||||
{
|
||||
++hits;
|
||||
float dx = hp.x-eye.x, dz = hp.z-eye.z;
|
||||
float r = (float)Sqrt(dx*dx + dz*dz);
|
||||
if (r < nearest) { nearest = r; ndir = (float)(th * 57.29578); }
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << "[wsweep] eye=(" << eye.x << "," << eye.z
|
||||
<< ") struct-hits=" << hits << "/24 nearest="
|
||||
<< (hits ? nearest : -1.0f) << " dir=" << ndir << "deg\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
float rs[3], rd[3];
|
||||
if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
|
||||
{
|
||||
@@ -4356,16 +4389,62 @@ void
|
||||
hotPoint = hp;
|
||||
}
|
||||
}
|
||||
if (hotTarget != 0)
|
||||
// WORLD-STRUCTURE pick (task #50): ray-test the STATIC collision
|
||||
// solid tree (garages/walls/props -- the geometry that already
|
||||
// BLOCKS the mech's walk) along the boresight. A structure CLOSER
|
||||
// than the mech hit occludes it (the shot stops at the wall); with
|
||||
// no mech, a structure hit IS the designation. This is the
|
||||
// authentic non-mech world pick: Mech::WorldStructurePick ->
|
||||
// Mover::FindStaticSolidHitBy, the "test against the static world"
|
||||
// half of the engine's own FindBoxedSolidHitBy (MOVER.cpp) -- so a
|
||||
// shot lands on exactly the geometry the walk collides against
|
||||
// (the user's "structures block the mech, but weapons pass through").
|
||||
Point3D structPoint;
|
||||
float structDist = 1e30f;
|
||||
bool haveStruct = false;
|
||||
if (gBTTerrainEntity != 0)
|
||||
{
|
||||
pickTarget = hotTarget;
|
||||
Point3D sp;
|
||||
if (WorldStructurePick(rayStart, rayDir, 1200.0f, &sp))
|
||||
{
|
||||
float dx = sp.x-rs[0], dy = sp.y-rs[1], dz = sp.z-rs[2];
|
||||
structDist = dx*dx + dy*dy + dz*dz;
|
||||
structPoint = sp;
|
||||
haveStruct = true;
|
||||
static float s_wp = 0.0f; s_wp += dt;
|
||||
if (getenv("BT_GROUND_LOG") && s_wp >= 1.0f)
|
||||
{
|
||||
s_wp = 0.0f;
|
||||
DEBUG_STREAM << "[wpick] structure hit at ("
|
||||
<< sp.x << "," << sp.y << "," << sp.z << ") dist="
|
||||
<< (float)Sqrt(structDist)
|
||||
<< (hotTarget ? " (mech also under boresight)" : "")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hotTarget != 0 && (!haveStruct || bestDist <= structDist))
|
||||
{
|
||||
pickTarget = hotTarget; // mech is the closest hit
|
||||
pickPoint = hotPoint;
|
||||
++gAimHits;
|
||||
}
|
||||
else if (haveStruct)
|
||||
{
|
||||
// a world structure is the closest hit (garage/wall) -- or it
|
||||
// occludes a mech behind it. Designate it as the non-mech world
|
||||
// pick (the geometry rides in the POINT; the sentinel entity has
|
||||
// no damage-zone table -> no lock ring, HudSim part_013.c:5620).
|
||||
pickTarget = gBTTerrainEntity;
|
||||
pickPoint = structPoint;
|
||||
++gAimGround;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the ground downrange of the guns (max = the pod's HUD
|
||||
// range scale; past it the shot is a sky shot -> no target)
|
||||
// no mech, no structure -> the flat ground downrange of the
|
||||
// guns (max = the pod's HUD range scale; past it the shot is a
|
||||
// sky shot -> no target)
|
||||
extern bool BTGroundRayHit(float,float,float, float,float,float,
|
||||
float, float*,float*,float*);
|
||||
float hx, hy, hz;
|
||||
|
||||
Reference in New Issue
Block a user