//###########################################################################
//
//							btvisgnd.cpp
//
// PORT ADDITION (presentation layer -- NOT a reconstruction): visual-ground
// conform for the external chase view.
//
// WHY: the mech SIM rides the coarse 1995 collision solids (cones/blocks;
// the authentic snap puts origin.y = solid surfaceY exactly -- see
// Mech::AuthenticGroundAndCollide).  The VISUAL terrain mesh runs 0..~2.1u
// ABOVE those solids on mound/mesa slopes (LOD0-exact measurement, dbase),
// so an upright mech's feet clip into the visible sand there.  In 1995 this
// was INVISIBLE: the game rendered cockpit-only (you can never see your own
// feet; other mechs are 100+m away).  Our port adds a close external camera,
// so the port also adds this presentation fix: sample the RENDERED terrain
// triangles under the mech and lift the render matrix (localToWorld ONLY --
// never localOrigin) by the visual-minus-solid gap.  Collision, aim, damage,
// network dead-reckoning all stay on the authentic solid model.
//
// Data source: the map's MakeMessage stream in BTL4.RES (class-42 static
// terrain instances) + each instance's LOD0 geometry via the engine BGF
// loader (bgfload.h).  No tuning constants -- the exact content geometry.
//
// Gate: BT_VISUAL_GROUND (default ON; =0 restores raw solid-height render).
// Diagnostics under BT_GROUND_LOG.
//
//###########################################################################

#include <bt.hpp>
#include <APP.hpp>			// application / GetCurrentMission / GetResourceFile
#include <MISSION.hpp>		// Mission::GetMapID
#include <bgfload.h>

#include <vector>
#include <string>
#include <map>
#include <cstring>
#include <cstdlib>
#include <cmath>

//---------------------------------------------------------------------------//
// Geometry cache
//---------------------------------------------------------------------------//
namespace
{

struct VgTri
{
	float ax, ay, az, bx, by, bz, cx, cy, cz;
};

struct VgMesh
{
	std::vector<VgTri> tris;
	float minx, maxx, minz, maxz;		// model-local XZ bound
};

struct VgInst
{
	int   mesh;							// index into gMeshes
	float px, py, pz;					// world placement
	float minx, maxx, minz, maxz;		// world XZ bound (placed)
};

std::vector<VgMesh> gMeshes;
std::vector<VgInst> gInsts;
int gState = 0;							// 0 = not tried, 1 = ready, -1 = unavailable

bool SkippedName(const char *n)
{
	// backdrop / sky pieces -- vertical scenery, never ground underfoot
	if (strstr(n, "sky") || strstr(n, "SKY")) return true;
	if (_strnicmp(n, "profile", 7) == 0) return true;
	if (_strnicmp(n, "rwin", 4) == 0) return true;
	return false;
}

int LoadMeshFor(const char *resource_name)
{
	static std::map<std::string, int> cache;	// name -> gMeshes idx (-1 = no geometry)
	std::string key(resource_name);
	std::map<std::string, int>::iterator it = cache.find(key);
	if (it != cache.end())
		return it->second;

	int idx = -1;
	BgfData data;
	std::string file = key + ".bgf";
	if (LoadBgfFile(file, data) && data.ok && !data.indices.empty())
	{
		VgMesh m;
		m.minx = m.minz = 1e9f;
		m.maxx = m.maxz = -1e9f;
		// The loader emits DOUBLE-SIDED triangles: emitTri pushes (a,b,c) then
		// (a,c,b) -- 6 indices per source triangle.  Take the forward one.
		for (size_t i = 0; i + 5 < data.indices.size(); i += 6)
		{
			const BgfVtx &A = data.verts[data.indices[i]];
			const BgfVtx &B = data.verts[data.indices[i + 1]];
			const BgfVtx &C = data.verts[data.indices[i + 2]];
			VgTri t = { A.x, A.y, A.z, B.x, B.y, B.z, C.x, C.y, C.z };
			m.tris.push_back(t);
			float xs[3] = { A.x, B.x, C.x }, zs[3] = { A.z, B.z, C.z };
			for (int k = 0; k < 3; ++k)
			{
				if (xs[k] < m.minx) m.minx = xs[k];
				if (xs[k] > m.maxx) m.maxx = xs[k];
				if (zs[k] < m.minz) m.minz = zs[k];
				if (zs[k] > m.maxz) m.maxz = zs[k];
			}
		}
		if (!m.tris.empty())
		{
			gMeshes.push_back(m);
			idx = (int)gMeshes.size() - 1;
		}
	}
	cache[key] = idx;
	return idx;
}

// Build the instance table from the CURRENT mission's map message stream.
// Map stream record layout (BTL4.RES, byte-verified):
//   [0]      u32  count
//   records: u32 mlen | i32 mid | u32 mflags | ... i32 class @+28 |
//            i32 resourceID @+40 | float pos[3] @+48 ; advance (mlen+3)&~3
// mflags bit1 = include-marker (skip); class 42 = static terrain/cultural.
bool BuildTables()
{
	if (application == 0) return false;
	Mission *mission = application->GetCurrentMission();
	ResourceFile *rf = application->GetResourceFile();
	if (mission == 0 || rf == 0) return false;

	ResourceDescription *map = rf->FindResourceDescription(mission->GetMapID());
	if (map == 0) return false;
	map->Lock();
	const unsigned char *base = (const unsigned char *)map->resourceAddress;
	size_t size = map->resourceSize;
	if (base == 0 || size < 4) return false;

	int count = *(const int *)base;
	size_t p = 4;
	int made = 0, skipped = 0;
	for (int i = 0; i < count && p + 52 <= size; ++i)
	{
		unsigned mlen   = *(const unsigned *)(base + p);
		unsigned mflags = *(const unsigned *)(base + p + 8);
		int      cls    = *(const int *)(base + p + 28);
		int      rid    = *(const int *)(base + p + 40);
		const float *pos = (const float *)(base + p + 48);
		p += (mlen + 3) & ~3u;

		if (mflags & 0x2) continue;			// include marker
		if (cls != 42) continue;			// terrain/static cultural only

		ResourceDescription *rd = rf->FindResourceDescription(rid);
		if (rd == 0) continue;
		if (SkippedName(rd->resourceName)) { ++skipped; continue; }

		int mi = LoadMeshFor(rd->resourceName);
		if (mi < 0) { ++skipped; continue; }

		VgInst inst;
		inst.mesh = mi;
		inst.px = pos[0]; inst.py = pos[1]; inst.pz = pos[2];
		inst.minx = gMeshes[mi].minx + pos[0];
		inst.maxx = gMeshes[mi].maxx + pos[0];
		inst.minz = gMeshes[mi].minz + pos[2];
		inst.maxz = gMeshes[mi].maxz + pos[2];
		gInsts.push_back(inst);
		++made;
	}
	if (getenv("BT_GROUND_LOG"))
		DEBUG_STREAM << "[visgnd] sampler ready: " << made << " terrain instances ("
			<< gMeshes.size() << " meshes, " << skipped << " skipped)\n" << std::flush;
	return made > 0;
}

// Highest visual surface at world (x,z) whose height lies in [yLo, yHi].
// The band keeps the query LOCAL: standing at a butte base it picks the skirt
// underfoot, not the mesa top 40u above.
bool SampleBand(float x, float z, float yLo, float yHi, float *out)
{
	bool found = false;
	float best = 0.0f;
	for (size_t ii = 0; ii < gInsts.size(); ++ii)
	{
		const VgInst &inst = gInsts[ii];
		if (x < inst.minx || x > inst.maxx || z < inst.minz || z > inst.maxz)
			continue;
		const float lx = x - inst.px, lz = z - inst.pz;
		const VgMesh &m = gMeshes[inst.mesh];
		for (size_t ti = 0; ti < m.tris.size(); ++ti)
		{
			const VgTri &t = m.tris[ti];
			// point-in-triangle (XZ), sign-consistent
			const float d1 = (t.bx - t.ax) * (lz - t.az) - (t.bz - t.az) * (lx - t.ax);
			const float d2 = (t.cx - t.bx) * (lz - t.bz) - (t.cz - t.bz) * (lx - t.bx);
			const float d3 = (t.ax - t.cx) * (lz - t.cz) - (t.az - t.cz) * (lx - t.cx);
			if (!((d1 >= 0 && d2 >= 0 && d3 >= 0) || (d1 <= 0 && d2 <= 0 && d3 <= 0)))
				continue;
			const float area = (t.bx - t.ax) * (t.cz - t.az) - (t.bz - t.az) * (t.cx - t.ax);
			if (area > -1e-6f && area < 1e-6f)
				continue;
			const float w1 = ((t.bx - lx) * (t.cz - lz) - (t.bz - lz) * (t.cx - lx)) / area;
			const float w2 = ((t.cx - lx) * (t.az - lz) - (t.cz - lz) * (t.ax - lx)) / area;
			const float w3 = 1.0f - w1 - w2;
			const float y = w1 * t.ay + w2 * t.by + w3 * t.cy + inst.py;
			if (y < yLo || y > yHi)
				continue;
			if (!found || y > best) { found = true; best = y; }
		}
	}
	if (found) *out = best;
	return found;
}

} // namespace

//---------------------------------------------------------------------------//
// BTVisualGroundLift -- render-only Y delta so the mech's feet meet the
// VISIBLE ground.  Returns 0 when gated off, unavailable, or no local surface.
//---------------------------------------------------------------------------//
float
	BTVisualGroundLift(float x, float y, float z)
{
	static int s_on = -1;
	if (s_on < 0)
	{
		const char *v = getenv("BT_VISUAL_GROUND");
		s_on = (v == 0 || v[0] != '0') ? 1 : 0;
	}
	if (!s_on)
		return 0.0f;

	if (gState == 0)
		gState = BuildTables() ? 1 : -1;
	if (gState != 1)
		return 0.0f;

	// Local band: up to 3u of visual rising above the solid (measured max ~2.2),
	// up to 1u of visual dipping below it.  Outside the band = not the ground
	// underfoot (cliff tops, building roofs) -> no conform.
	float surf;
	if (!SampleBand(x, z, y - 1.0f, y + 3.0f, &surf))
		return 0.0f;
	float lift = surf - y;
	if (lift < -1.0f) lift = -1.0f;
	if (lift > 3.0f)  lift = 3.0f;
	return lift;
}

//---------------------------------------------------------------------------//
// BTGroundRayHit -- march a ray (o + dir*t) against the visible terrain and
// return the first point where it meets the surface (rising ground: butte
// faces, dune fronts).  Used by the weapon fire path to end the beam + place
// the impact on the ground.  Returns false (no hit) for a level shot over open
// desert -- the caller then flies the beam to max range.
//---------------------------------------------------------------------------//
bool
	BTGroundRayHit(float ox, float oy, float oz,
				   float dx, float dy, float dz,
				   float maxDist, float *hx, float *hy, float *hz)
{
	if (gState == 0)
		gState = BuildTables() ? 1 : -1;
	if (gState != 1)
		return false;

	const float step = 4.0f;
	for (float t = step; t <= maxDist; t += step)
	{
		const float x = ox + dx * t;
		const float y = oy + dy * t;
		const float z = oz + dz * t;
		float surf;
		// wide downward band: catch terrain rising into a mostly-horizontal ray.
		if (SampleBand(x, z, y - 90.0f, y + 6.0f, &surf) && y <= surf)
		{
			*hx = x; *hy = surf; *hz = z;
			return true;
		}
	}
	return false;
}
