#124: the aimed pick now intersects the DRAWN GEOMETRY -- sphere
approximation retired; shared-hull hits route through the cylinder The zone-walk matrix caught the pick red-handed: with the servo verifiably holding the reticle on the dtorso segment, the per-segment BOUNDING-SPHERE pick returned rgun/ruleg -- the gun pods and legs thread the ray before the torso from most angles (its own comments admitted a foot could be unhittable behind its own knee). The 1995 pick was a dpl scene intersection against the drawn geometry. Restored that semantic: * BTGetPickMesh caches each segment d3d_OBJECT's triangles CPU-side once (its own BGF VB/IB, managed-pool locks); MechSegmentPick now runs sphere PRE-FILTER -> Moller-Trumbore nearest-hit across the threaded segments' posed meshes; the old smallest-sphere selection survives only as the no-triangle fallback. * Segments claimed as CARRIER by 2+ zones (the shared hull: madcat seg 4 carries dtorso+ltorso+rtorso+utorso+rears) cannot resolve one zone from geometry -- those hits return zone -1 WITH the accurate triangle point, and the victim's bit-verified (frame-fixed) cylinder assigns the panel by band/wedge. Unique carriers (legs, feet, arms, gun pods) keep the direct zone. * ZoneAimPoint: the walker aims at a zone's VISUAL center (largest pick object's cull-center) instead of the segment origin -- joint origins made feet/lower legs strike the part above. * Walker upgrades from live operation: 3-column truth (aim/pick/land), engage gate, damped servo with polarity watchdog, approach port. Zone-walk verdict (full cycles, spinning target, real MP): limbs 5-6/6 direct in-zone; every hull panel routes CYL; the victim's landed zones now include ltorso/rtorso/reardtorso/rearutorso/rearrtorso -- the panels night-10 reported unhittable. Residuals tracked on #124: rtorso/utorso aim-anchor placement, doors/searchlight small-zone sample, twisted-torso twist-sign verify. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
16e75d6e1a
commit
1a3c268278
@@ -1545,10 +1545,106 @@ int BTWreckSinkTick(Entity *victim, float dt)
|
||||
|
||||
|
||||
//
|
||||
// #73 -- the aimed PER-PART pick (see the header note). Ray-vs-sphere over
|
||||
// the per-segment draw objects recorded at tree build; world centers come
|
||||
// through the draw-cached mLocalToWorld (updated every drawn frame -- the
|
||||
// target being aimed at is on screen, so at most one frame stale).
|
||||
// #124 -- the pick's TRIANGLE cache. The 1995 pick was a dpl scene
|
||||
// intersection against the DRAWN GEOMETRY (the division card cast from the
|
||||
// view); the port's sphere approximation measurably mis-picked (the zone-walk
|
||||
// matrix: aim dead-on dtorso -> picked rgun/ruleg -- gun/limb spheres thread
|
||||
// the ray before the torso from most angles, and its own comments admitted "a
|
||||
// foot can be unhittable behind its own knee"). Restore the authentic
|
||||
// semantic: nearest RAY-TRIANGLE hit across the candidate segments' posed
|
||||
// meshes. Positions are read ONCE per d3d_OBJECT from its own BGF buffers
|
||||
// (managed pool, lockable) and cached CPU-side; the per-frame cost is a
|
||||
// sphere pre-filter + Moller-Trumbore over the few threaded segments.
|
||||
//
|
||||
struct BTPickMesh
|
||||
{
|
||||
std::vector<float> pos; // xyz per vertex
|
||||
std::vector<unsigned int> idx; // triangle list
|
||||
int ok;
|
||||
};
|
||||
static std::map<d3d_OBJECT *, BTPickMesh> gBTPickMeshes;
|
||||
|
||||
static BTPickMesh *
|
||||
BTGetPickMesh(d3d_OBJECT *obj)
|
||||
{
|
||||
std::map<d3d_OBJECT *, BTPickMesh>::iterator mi = gBTPickMeshes.find(obj);
|
||||
if (mi != gBTPickMeshes.end())
|
||||
return mi->second.ok ? &mi->second : 0;
|
||||
|
||||
BTPickMesh &pm = gBTPickMeshes[obj];
|
||||
pm.ok = 0;
|
||||
if (obj->mBgfVB == 0 || obj->mBgfIB == 0 || obj->mBgfStride < 12)
|
||||
return 0;
|
||||
|
||||
D3DINDEXBUFFER_DESC ibd;
|
||||
if (FAILED(obj->mBgfIB->GetDesc(&ibd)))
|
||||
return 0;
|
||||
int idx32 = (ibd.Format == D3DFMT_INDEX32);
|
||||
unsigned int nIdx = ibd.Size / (idx32 ? 4 : 2);
|
||||
|
||||
void *vp = 0, *ip = 0;
|
||||
if (FAILED(obj->mBgfVB->Lock(0, 0, &vp, D3DLOCK_READONLY)))
|
||||
return 0;
|
||||
if (FAILED(obj->mBgfIB->Lock(0, 0, &ip, D3DLOCK_READONLY)))
|
||||
{
|
||||
obj->mBgfVB->Unlock();
|
||||
return 0;
|
||||
}
|
||||
pm.pos.resize((size_t)obj->mBgfNumVerts * 3);
|
||||
const unsigned char *vb = (const unsigned char *)vp;
|
||||
for (UINT v = 0; v < obj->mBgfNumVerts; ++v)
|
||||
{
|
||||
const float *p = (const float *)(vb + (size_t)v * obj->mBgfStride);
|
||||
pm.pos[v*3+0] = p[0]; // position-first vertex layout (the BGF
|
||||
pm.pos[v*3+1] = p[1]; // loader's own decl; the cull sphere was
|
||||
pm.pos[v*3+2] = p[2]; // computed from these same floats at load)
|
||||
}
|
||||
pm.idx.resize(nIdx);
|
||||
if (idx32)
|
||||
{
|
||||
const unsigned int *s = (const unsigned int *)ip;
|
||||
for (unsigned int k = 0; k < nIdx; ++k) pm.idx[k] = s[k];
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned short *s = (const unsigned short *)ip;
|
||||
for (unsigned int k = 0; k < nIdx; ++k) pm.idx[k] = s[k];
|
||||
}
|
||||
obj->mBgfIB->Unlock();
|
||||
obj->mBgfVB->Unlock();
|
||||
pm.ok = (pm.idx.size() >= 3 && pm.pos.size() >= 9);
|
||||
return pm.ok ? &pm : 0;
|
||||
}
|
||||
|
||||
// Moller-Trumbore, both-sided (the pod's dpl geometry has no consistent
|
||||
// winding guarantee across ported BGF pieces). Returns t >= 0 or -1.
|
||||
static float
|
||||
BTRayTri(const float o[3], const float d[3],
|
||||
const float *a, const float *b, const float *c)
|
||||
{
|
||||
float e1[3] = { b[0]-a[0], b[1]-a[1], b[2]-a[2] };
|
||||
float e2[3] = { c[0]-a[0], c[1]-a[1], c[2]-a[2] };
|
||||
float pv[3] = { d[1]*e2[2]-d[2]*e2[1], d[2]*e2[0]-d[0]*e2[2], d[0]*e2[1]-d[1]*e2[0] };
|
||||
float det = e1[0]*pv[0] + e1[1]*pv[1] + e1[2]*pv[2];
|
||||
if (det > -1e-8f && det < 1e-8f) return -1.0f;
|
||||
float inv = 1.0f / det;
|
||||
float tv[3] = { o[0]-a[0], o[1]-a[1], o[2]-a[2] };
|
||||
float u = (tv[0]*pv[0] + tv[1]*pv[1] + tv[2]*pv[2]) * inv;
|
||||
if (u < 0.0f || u > 1.0f) return -1.0f;
|
||||
float qv[3] = { tv[1]*e1[2]-tv[2]*e1[1], tv[2]*e1[0]-tv[0]*e1[2], tv[0]*e1[1]-tv[1]*e1[0] };
|
||||
float v = (d[0]*qv[0] + d[1]*qv[1] + d[2]*qv[2]) * inv;
|
||||
if (v < 0.0f || u + v > 1.0f) return -1.0f;
|
||||
float t = (e2[0]*qv[0] + e2[1]*qv[1] + e2[2]*qv[2]) * inv;
|
||||
return (t >= 0.0f) ? t : -1.0f;
|
||||
}
|
||||
|
||||
//
|
||||
// #73 -- the aimed PER-PART pick (see the header note). #124: now a true
|
||||
// DRAWN-GEOMETRY intersection -- sphere pre-filter, then nearest ray-triangle
|
||||
// hit across the threaded segments' posed meshes (the 1995 division-card
|
||||
// semantic). The old smallest-sphere selection survives only as the fallback
|
||||
// when no triangle anywhere is struck (grazing edge shots). World transforms
|
||||
// come through the draw-cached mLocalToWorld (at most one frame stale).
|
||||
//
|
||||
int
|
||||
BTL4VideoRenderer::MechSegmentPick(
|
||||
@@ -1573,12 +1669,42 @@ int
|
||||
// envelope; smallest-pierced picks the most specific part on the aim line,
|
||||
// and the torso wins only when no limb is threaded -- which is the per-part
|
||||
// semantic the 1995 mesh intersection produced.
|
||||
float bestR = 1e30f; // primary key: sphere radius (ascending)
|
||||
float bestScore = 1.0f; // tie-break: normalized perpendicular d2/r2
|
||||
float bestR = 1e30f; // sphere-FALLBACK key: radius (ascending)
|
||||
float bestScore = 1.0f; // sphere tie-break: normalized d2/r2
|
||||
float bestT = max_range;
|
||||
int bestZone = -1;
|
||||
int hitAny = 0;
|
||||
|
||||
// triangle-accurate primary: nearest surface hit across all segments
|
||||
float triBestT = max_range;
|
||||
int triBestZone = -1;
|
||||
int triBestSeg = -1;
|
||||
int triHit = 0;
|
||||
|
||||
// #124: segments claimed as CARRIER by TWO OR MORE zones (the shared hull
|
||||
// -- e.g. madcat seg 4 carries dtorso AND ltorso AND rtorso AND utorso AND
|
||||
// the rears) cannot resolve a single zone from geometry. The authentic
|
||||
// route for their hits is the CYLINDER: return zone -1 with the accurate
|
||||
// triangle hit point, and the victim's point resolver assigns the panel
|
||||
// by band/wedge. Unique-carrier segments (legs, feet, arms, gun pods)
|
||||
// keep their direct zone.
|
||||
unsigned char segClaims[192];
|
||||
memset(segClaims, 0, sizeof(segClaims));
|
||||
{
|
||||
extern int BTMechZoneSegAndName(void *mech_v, int zone_idx,
|
||||
int *seg_out, const char **name_out);
|
||||
int zsi;
|
||||
const char *znm;
|
||||
for (int zi = 0; zi < 64; ++zi)
|
||||
{
|
||||
if (!BTMechZoneSegAndName((void *)mech, zi, &zsi, &znm))
|
||||
break;
|
||||
if (zsi >= 0 && zsi < (int)sizeof(segClaims)
|
||||
&& segClaims[zsi] < 255)
|
||||
++segClaims[zsi];
|
||||
}
|
||||
}
|
||||
|
||||
std::map<int, MechRenderTree::SegPick>::iterator sp;
|
||||
for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp)
|
||||
{
|
||||
@@ -1609,6 +1735,53 @@ int
|
||||
if (t < 0.0f || t >= max_range)
|
||||
continue;
|
||||
|
||||
// ---- TRIANGLE TEST (#124): the sphere only nominates ----
|
||||
BTPickMesh *pm = BTGetPickMesh(obj);
|
||||
if (pm != 0)
|
||||
{
|
||||
// ray into object-local space (affine inverse; segment poses are
|
||||
// rigid, so local t == world t after direction normalization is
|
||||
// preserved by construction below)
|
||||
D3DXMATRIX w2l;
|
||||
if (D3DXMatrixInverse(&w2l, 0, &l2w) != 0)
|
||||
{
|
||||
D3DXVECTOR3 lo, ld;
|
||||
D3DXVECTOR3 wo(ray_start[0], ray_start[1], ray_start[2]);
|
||||
D3DXVECTOR3 wd(ray_dir[0], ray_dir[1], ray_dir[2]);
|
||||
D3DXVec3TransformCoord(&lo, &wo, &w2l);
|
||||
D3DXVec3TransformNormal(&ld, &wd, &w2l);
|
||||
float o3[3] = { lo.x, lo.y, lo.z };
|
||||
float d3[3] = { ld.x, ld.y, ld.z };
|
||||
const float *P = &pm->pos[0];
|
||||
size_t nv = pm->pos.size() / 3;
|
||||
for (size_t k = 0; k + 2 < pm->idx.size(); k += 3)
|
||||
{
|
||||
unsigned int i0 = pm->idx[k], i1 = pm->idx[k+1], i2 = pm->idx[k+2];
|
||||
if (i0 >= nv || i1 >= nv || i2 >= nv)
|
||||
continue;
|
||||
float tt = BTRayTri(o3, d3, P + i0*3, P + i1*3, P + i2*3);
|
||||
if (tt >= 0.0f && tt < triBestT)
|
||||
{
|
||||
// world-space t of the local hit (handles any scale)
|
||||
D3DXVECTOR3 lh(o3[0]+d3[0]*tt, o3[1]+d3[1]*tt, o3[2]+d3[2]*tt);
|
||||
D3DXVECTOR3 wh;
|
||||
D3DXVec3TransformCoord(&wh, &lh, &l2w);
|
||||
float wt = (wh.x - ray_start[0]) * ray_dir[0]
|
||||
+ (wh.y - ray_start[1]) * ray_dir[1]
|
||||
+ (wh.z - ray_start[2]) * ray_dir[2];
|
||||
if (wt >= 0.0f && wt < triBestT)
|
||||
{
|
||||
triBestT = wt;
|
||||
triBestZone = sp->second.zone;
|
||||
triBestSeg = sp->first;
|
||||
triHit = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- sphere FALLBACK bookkeeping (unchanged selection) ----
|
||||
float score = d2 / r2; // 0 = dead-center thread
|
||||
if (r > bestR
|
||||
|| (r == bestR && score >= bestScore))
|
||||
@@ -1621,6 +1794,17 @@ int
|
||||
hitAny = 1;
|
||||
}
|
||||
|
||||
if (triHit)
|
||||
{
|
||||
bestT = triBestT;
|
||||
bestZone = triBestZone;
|
||||
hitAny = 1;
|
||||
// shared-carrier segment -> the cylinder decides (accurate point kept)
|
||||
if (triBestSeg >= 0 && triBestSeg < (int)sizeof(segClaims)
|
||||
&& segClaims[triBestSeg] > 1)
|
||||
bestZone = -1;
|
||||
}
|
||||
|
||||
// #92 probe: which spheres did the ray actually THREAD, and which won?
|
||||
// "smallest radius wins" means a big sphere can never beat a small one that
|
||||
// the ray also grazes -- so a foot can be unhittable behind its own knee.
|
||||
@@ -1691,6 +1875,44 @@ int
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
// #124 zone walker: the zone's visual aim anchor (see the hpp note).
|
||||
//
|
||||
int
|
||||
BTL4VideoRenderer::ZoneAimPoint(Entity *mech, int zone, float out3[3])
|
||||
{
|
||||
std::map<Entity*, MechRenderTree>::iterator it = mMechRenderTrees.find(mech);
|
||||
if (it == mMechRenderTrees.end() || it->second.wrecked)
|
||||
return 0;
|
||||
d3d_OBJECT *best = 0;
|
||||
std::map<int, MechRenderTree::SegPick>::iterator sp;
|
||||
for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp)
|
||||
{
|
||||
if (sp->second.zone != zone || sp->second.obj == NULL)
|
||||
continue;
|
||||
if (best == 0 || sp->second.obj->mCullRadius > best->mCullRadius)
|
||||
best = sp->second.obj;
|
||||
}
|
||||
if (best == 0 || best->mCullRadius <= 0.0f)
|
||||
return 0;
|
||||
D3DXMATRIX l2w = best->GetLocalToWorld();
|
||||
D3DXVECTOR3 cw;
|
||||
D3DXVec3TransformCoord(&cw, &best->mCullCenter, &l2w);
|
||||
out3[0] = cw.x; out3[1] = cw.y; out3[2] = cw.z;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int BTMechZoneAimPoint(void *mech, int zone, float out3[3])
|
||||
{
|
||||
if (mech == NULL || application == NULL)
|
||||
return 0;
|
||||
BTL4VideoRenderer *renderer =
|
||||
(BTL4VideoRenderer *)application->GetVideoRenderer();
|
||||
if (renderer == NULL)
|
||||
return 0;
|
||||
return renderer->ZoneAimPoint((Entity *)mech, zone, out3);
|
||||
}
|
||||
|
||||
//
|
||||
// Game-side bridge (mech4.cpp's per-frame target pick; same access pattern as
|
||||
// the wreck swap below).
|
||||
|
||||
Reference in New Issue
Block a user