//===========================================================================//
// File:	btl4rdr.cpp                                                         //
// Project: BattleTech          Brick: Gauge Renderer Manager                 //
// Contents: MapDisplay -- the cockpit radar / threat-map gauge.  Draws a     //
//           top-down (X/Z) map centred on the operator mech, plots blips for //
//           nearby static and moving entities, the player's view wedge, and  //
//           floating name labels for tracked mechs.                          //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 09/13/95  CPB  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1994, Virtual World Entertainment, Inc. All rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE).  Behaviour follows the
// Ghidra pseudo-C captured in recovered/all/part_013.c; the class/member names
// are taken verbatim from the SURVIVING header BTL4RDR.HPP, whose member
// byte-offsets match the decompiled object exactly:
//
//      offsetPosition          @0x2D0      backgroundColor    @0x358
//      currentScale            @0x2D4      staticColor        @0x35C
//      currentPositionPointer  @0x2D8      boxColor           @0x360
//      currentAngularPointer   @0x2DC      halfWidth          @0x364
//      currentCapabilitiesRatio@0x2E0      halfHeight         @0x368
//      LODIndex                @0x2E4      operating          @0x36C
//      pixelsPerMeter          @0x2E8      rockAndRoll        @0x370
//      metersPerPixel          @0x2EC      flashState         @0x374
//      maximumRange            @0x2F0      staticEntityList   @0x378
//      maximumDistanceSquared  @0x2F4      movingEntityList   @0x394
//      xMin/yMin/zMin          @0x2F8..    previousDrawing    @0x3B0
//      xMax/yMax/zMax          ..0x30C     halfViewWidthInUnits@0x3C0
//      viewingPosition         @0x310      shadowTable        @0x3C4
//      viewHorizontalRotation  @0x31C      nameArray[16]      @0x090 (stride 0x24)
//      viewHalfHorizontalWidth @0x320
//      worldToView             @0x324      (base GraphicGauge: renderer@0x1C,
//                                           embedded GraphicsView@0x48)
//
// ---------------------------------------------------------------------------
// PORT NOTE.  This module is compiled against the surviving WinTesla (RP411)
// MUNGA / MUNGA_L4 engine headers.  Several facilities the original BattleTech
// radar leaned on are BT-specific and are NOT present in that engine:
//      * the per-Entity "video object" (a name-bitmap id + current target),
//      * the radar "pip" symbol table,
//      * the gauge-renderer's spatial worlds, operator-entity connection and
//        pip table,
//      * the global name-bitmap cache (the DAT_004efc94 App singleton).
// They are reproduced as a file-local adaptation layer below so the recovered
// MapDisplay logic keeps its full structure and compiles; the bodies are
// best-effort and currently resolve to "unavailable" in this engine build
// (see the reconstruction CROSS-FAMILY NEEDS).  All engine 2D/matrix calls
// have been retargeted to the real WinTesla API names:
//      GraphicsView::MoveTo->MoveToAbsolute, LineTo->DrawLineToAbsolute,
//      Blit->DrawBitMap, BeginClip->AttachRecorder, EndClip->DetachRecorder,
//      SetExtent->SetPositionWithinPort;  GraphicsViewRecord::Erase->Draw,
//      Reset->Clear;  AffineMatrix::Identity->BuildIdentity,
//      SetTranslation->SetFromAxis(W_Axis), Concatenate->operator*=,
//      Scale->Multiply(m,v), FromQuaternion->operator=, Transform->point*=m;
//      BitMap width/height -> Data.Size.x/.y;  the embedded GraphicsView is the
//      base GraphicGauge::localView.
// ---------------------------------------------------------------------------
//

#include <bt.hpp>
#pragma hdrstop

#if !defined(BTL4RDR_HPP)
#	include <btl4rdr.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif

#include <iostream>
#include <cstring>
#include <new>

//
// Tuning constants recovered from the CODE constant pool.
//
static const Scalar CalcBoundsEpsilon = 0.0f;		// _DAT_004c2284
static const Scalar MetersPerPixelNum = 1.0f;		// _DAT_004c2288
static const Scalar OperatingEpsilon  = 0.0f;		// _DAT_004c2174
static const Scalar HeadingHalf       = 0.5f;		// _DAT_004c2480 (half-angle)
static const Scalar ViewWedgeRadius   = 10000.0f;	// _DAT_004c25ac
static const Scalar MapVerticalSpan   = 32768.0f;	// 0x47000000 / 0xc7000000

//
// FUN_004c2f88 -- the fixed blip / name colour index used throughout the map.
// (Returns the literal 7 in the shipped binary.)
//
static int
	MapBlipColor()
{
	return 7;
}


//###########################################################################
//###########################################################################
//	BT radar adaptation layer (file-local -- see PORT NOTE above)
//###########################################################################
//###########################################################################

//
// A tracked entity's "video object": carries the name-bitmap id used for the
// floating label and the entity's current weapons target.  (BT engine concept;
// absent from the WinTesla Entity.)
//
class VideoObject
{
public:
	int		nameID;
	Entity	*target;
};

//
// One radar symbol ("pip") + the per-model lookup table that owns them.
//
class Pip
{
public:
	void
		Draw(
			Scalar			lod_index,
			Scalar			meters_per_pixel,
			GraphicsView	*view,
			int				color,
			AffineMatrix	&transform,
			int				box_color
		);
};

class PipTable
{
public:
	Pip *
		LookUpPip(int model_class_ID, int variant);
};

//
// Entity placement helpers -- the WinTesla Entity keeps its world placement in
// localOrigin (linearPosition + angularPosition).  These give the radar the
// Point3D position and AffineMatrix transform the recovered code expects.
//
static inline Point3D &
	EntityPosition(Entity *entity)
{
	return entity->localOrigin.linearPosition;
}

static inline void
	EntityTransform(AffineMatrix *out, Entity *entity)
{
	*out = entity->localOrigin;
}

//
// BT-renderer facilities not present on the WinTesla GaugeRenderer.
//
static Entity *
	ResolveOperatorEntity(GaugeRenderer *renderer);
static PipTable *
	GetPipTable(GaugeRenderer *renderer);

//
// Per-entity video object, name-bitmap cache, and the "labelled static entity"
// test (nav beacons etc. carry a name bitmap).
//
static VideoObject *
	GetVideoObject(Entity *entity);
static BitMap *
	LookUpNameBitmap(int name_ID);
static int
	GetNameID(Entity *entity);
static Logical
	IsLabelledEntity(Entity *entity);

//
// The owner mech's current target (via its video object).
//
static Entity *
	GetTarget(Entity *owner);

//
// Mech sub-object lookup ("Torso") + its horizontal rotation feed.
//
static Entity *
	FindSubObject(Entity *entity, const char *name);
static Radian *
	GetHorizontalRotation(Entity *entity);


//###########################################################################
//###########################################################################
//					MapName
//###########################################################################
//###########################################################################

//
// @004c19fc -- snapshot a tracked entity into one name-array slot: project its
// world position into the (already scaled) view space, look up its name bitmap
// from the global font/name cache, and flag whether it is the hot-boxed target.
//
void
	MapName::ExtractFromEntity(
		Entity			*entity,
		AffineMatrix	&worldToView,
		Entity			*exclude_entity,
		Entity			*hotbox_entity
	)
{
	//
	// The operator's own mech (exclude_entity) draws no name.
	//
	if (entity == exclude_entity)
	{
		nameBitmap = NULL;					// param_1[6] = 0
		return;
	}

	VideoObject *video_object = GetVideoObject(entity);		// *(entity+400)
	if (video_object == NULL)
	{
		nameBitmap = NULL;
	}
	else
	{
		//
		// Resolve the entity's name id through the global name-bitmap cache.
		//
		int nameID = video_object->nameID;					// *(vobj+0x1E0)
		nameBitmap = LookUpNameBitmap(nameID);				// cache vtbl+0x0C
	}

	//
	// Project the entity origin into screen space and stamp the label.
	//
	center.Multiply(EntityPosition(entity), worldToView);	// FUN_00408b98
	color    = MapBlipColor();								// 7
	hotBoxed = (entity == hotbox_entity);

	//
	// The decompiled object always parks the name's pointer direction at
	// (-1, 0, 0) -- labels are screen-aligned, not entity-aligned.
	//
	direction.x = -1.0f;
	direction.y = 0.0f;
	direction.z = 0.0f;
}

//
// @004c1ab0 -- blit one extracted name bitmap at its projected screen point,
// in either its own colour or (when hot-boxed) the supplied highlight colour.
//
void
	MapName::Draw(
		GraphicsView	*graphics_view,
		int				hotbox_color
	)
{
	if (nameBitmap == NULL)
	{
		return;
	}

	graphics_view->MoveToAbsolute(Round(center.x), Round(center.z));	// +0x24, FUN_004dcd94

	if (!hotBoxed)
	{
		graphics_view->SetColor(color);						// +0x18
	}
	else
	{
		graphics_view->SetColor(hotbox_color);
	}

	graphics_view->DrawBitMap(								// +0x54
		0, nameBitmap, 0, 0,
		nameBitmap->Data.Size.x - 1,
		nameBitmap->Data.Size.y - 1
	);
}


//###########################################################################
//###########################################################################
//					MapDisplay
//###########################################################################
//###########################################################################

//#############################################################################
// Construction / Destruction
//
// @004c1c24 -- vtable 0051422c, base name "MapDisplay".  Builds the 16-entry
// name array, the static/moving entity lists and the previous-drawing record,
// sizes the graphics port from (left,bottom,right,top), allocates the radar
// shadow table, and wires the four data-source gauge connections
// (scale, position, angle, capabilities) plus an optional torso-rotation feed.
//
MapDisplay::MapDisplay(
	GaugeRate			rate,
	ModeMask			mode_mask,
	L4GaugeRenderer		*renderer,
	unsigned int		owner_ID,
	int					graphics_port_number,
	int					left,
	int					bottom,
	int					right,
	int					top,
	OffsetPosition		offset_position,
	int					view_width_in_degrees,
	int					bg_color,
	int					static_color,
	int					hotbox_color,
	Scalar				maximum_range,
	Entity				*entity,
	Scalar				*scale_value_pointer,
	Point3D				**position_pointer,
	Quaternion			**angle_pointer,
	Scalar				*capabilities_ratio,
	Logical				allow_rock_and_roll
):
	GraphicGauge(						// FUN_00444818
		rate, mode_mask, renderer, owner_ID, graphics_port_number, "MapDisplay"
	)
{
	//
	// nameArray / staticEntityList / movingEntityList / previousDrawing are
	// member objects -- the decompiler's explicit Construct_Array / ctor thunks
	// are the compiler-generated member construction; nothing to do here.
	//

	//
	// Size the graphics port and centre the origin.
	//
	localView.SetPositionWithinPort(left, bottom, right, top);	// (this+0x48 vtbl+0x08)
	if (offset_position == MapDisplay::bottom)
	{
		localView.SetOrigin((right - left) >> 1, 0);				// vtbl+0x10
	}
	else
	{
		localView.SetOrigin((right - left) >> 1, (top - bottom) >> 1);
	}

	backgroundColor	= bg_color;					// this[0xD6]
	staticColor		= static_color;				// this[0xD7]
	boxColor		= hotbox_color;				// this[0xD8]
	maximumRange	= maximum_range;			// this[0xBC]
	rockAndRoll		= allow_rock_and_roll;		// this[0xDC]

	halfWidth		= (right - left)   >> 1;	// this[0xD9]
	halfHeight		= (top - bottom)   >> 1;	// this[0xDA]
	offsetPosition	= offset_position;			// this[0xB4]

	halfViewWidthInUnits = view_width_in_degrees >> 1;					// this[0xF0]
	shadowTable		= (ShadowRecord *)operator new(halfViewWidthInUnits * 8);	// this[0xF1]

	viewHorizontalRotation	= 0.0f;				// this[199]  (0x31C)
	viewHalfHorizontalWidth	= 0.3926991f;		// this[200]  (0x320) = PI/8
	flashState		= False;					// this[0xDD]

	//
	// Data-source connections: each is a small heap GaugeConnection wired so
	// that the gauge framework copies the external value into a member field
	// every frame.  scale -> currentScale, position -> currentPositionPointer,
	// angle -> currentAngularPointer, capabilities -> currentCapabilitiesRatio.
	//
	AddConnection(										// (*this+0x34)
		new GaugeConnectionDirectOf<Scalar>(0, &currentScale, scale_value_pointer));			// FUN_00474855
	AddConnection(
		new GaugeConnectionDirectOf<Point3D*>(0, &currentPositionPointer, position_pointer));// FUN_004c2a3e
	AddConnection(
		new GaugeConnectionDirectOf<Quaternion*>(0, &currentAngularPointer, angle_pointer));	// FUN_004c2bc0
	AddConnection(
		new GaugeConnectionDirectOf<Scalar>(0, &currentCapabilitiesRatio, capabilities_ratio));// FUN_00474855

	//
	// If the host entity is a mech (class id 0xBB9) with a "Torso" sub-object,
	// feed its horizontal rotation into the map's view heading.
	//
	if (entity->GetClassID() == 0xBB9)
	{
		Entity *torso = FindSubObject(entity, "Torso");		// FUN_0041f98c
		if (torso != NULL)
		{
			AddConnection(
				new GaugeConnectionDirectOf<Radian>(			// FUN_004c2d42
					0, &viewHorizontalRotation, GetHorizontalRotation(torso) /*+0x1D8*/));
		}
	}
}

//
// @004c1ea8 -- free the shadow table.  previousDrawing, the two entity lists
// and the name array are member objects and are destroyed automatically after
// this body runs (the decompiler's explicit dtor thunks are that automatic
// member destruction), so only the heap shadowTable is released here.
//
MapDisplay::~MapDisplay()
{
	operator delete(shadowTable);				// FUN_004022E8(this[0xF1])
	// base ~GraphicGauge -- FUN_00444870
}

//
// @004c1f30
//
Logical
	MapDisplay::TestInstance() const
{
	return GraphicGauge::TestInstance();		// FUN_004448ac
}

//
// @004c1f40 -- emit "ThreatIndicator:" then chain to the base ShowInstance
// with a deeper (tab) indent.
//
void
	MapDisplay::ShowInstance(char *indent)
{
	std::cout << indent << "ThreatIndicator:";	// FUN_004dbb24 x2

	char deeper[80];
	strcpy(deeper, indent);
	strcat(deeper, "\t");						// FUN_004d49b8(local_54, "\t")

	GraphicGauge::ShowInstance(deeper);			// FUN_004448bc
}

//
// @004c1fc0 -- reset the per-frame phase counter when the gauge is shown.
//
void
	MapDisplay::BecameActive()
{
	operatingPhase = 0;							// this+0x354
}

//
// @004c1fd0 -- the per-frame work, spread across four ticks (phases 0..3) so
// the cost is amortised:
//      0  decide whether the radar is powered, compute range, set up bounds
//      1  spatial-query static (terrain/structure) entities into the list
//      2  spatial-query moving (mech/vehicle) entities into the list
//      3  erase the previous frame and redraw the map
//
void
	MapDisplay::Execute()
{
	int phase = operatingPhase++;				// this+0x354

	if (phase == 0)
	{
		operating = (OperatingEpsilon < currentCapabilitiesRatio);	// 0.0f < ratio
		if (operating)
		{
			maximumDistanceSquared = currentCapabilitiesRatio * maximumRange;
			maximumDistanceSquared = maximumDistanceSquared * maximumDistanceSquared;
			CalculateBounds();						// FUN_004c2178
		}
	}
	else if (phase == 1)
	{
		staticEntityList.Clear();					// FUN_004435ac(this+0x378)
		if (operating)
		{
			renderer->GetStaticEntitiesWithinBounds(	// FUN_00443ba4(renderer+0xB0)
				&staticEntityList,
				xMin, yMin, zMin, xMax, yMax, zMax);
		}
	}
	else if (phase == 2)
	{
		movingEntityList.Clear();					// FUN_004435ac(this+0x394)
		if (operating)
		{
			renderer->GetMovingEntitiesWithinBounds(	// FUN_0044366c(renderer+0x94)
				&movingEntityList,
				xMin, yMin, zMin, xMax, yMax, zMax);
		}
	}
	else
	{
		if (phase == 3)
		{
			EraseDisplay();							// FUN_004c2294
			if (operating)
			{
				DrawDisplay();						// FUN_004c22c4
			}
		}
		operatingPhase = 0;
	}
}

//
// @004c2178  (file=bt/heat... actually bt_l4/btl4rdr.cpp -- CONFIRMED by the
// embedded assert path "d:\tesla\bt\bt_l4\BTL4RDR.CPP", line 0x224).
//
// Derive the metres<->pixels scale from the current zoom (currentScale) and
// build the world-space axis-aligned box that the spatial queries will use.
// The map is top-down, so the X and Z bounds hug the viewing position while
// the Y (vertical) bounds are opened wide (+/-32768).
//
void
	MapDisplay::CalculateBounds()
{
	if (currentScale <= CalcBoundsEpsilon)
	{
		Verify(False);						// FUN_0040385c
											// "MapDisplay::CalculateBounds detected a bad scale"
											// "d:\\tesla\\bt\\bt_l4\\BTL4RDR.CPP", 0x224
		currentScale = 1.0f;			// 0x3f800000
	}

	pixelsPerMeter = (Scalar)(halfWidth * 2) / currentScale;
	metersPerPixel = MetersPerPixelNum / pixelsPerMeter;	// 1.0 / pixelsPerMeter
	LODIndex       = metersPerPixel;

	Scalar halfWorldWidth  = (Scalar)halfWidth  / pixelsPerMeter;
	Scalar halfWorldHeight = (Scalar)halfHeight / pixelsPerMeter;

	Entity *owner = ResolveOperatorEntity(renderer);	// FUN_00417ab4(renderer+0x40)
	viewingPosition = EntityPosition(owner);			// FUN_00408440(this+0x310, owner+0x100)

	xMin = viewingPosition.x - halfWorldWidth;
	xMax = viewingPosition.x + halfWorldWidth;
	yMin = -MapVerticalSpan;			// 0xc7000000
	yMax =  MapVerticalSpan;			// 0x47000000
	zMin = viewingPosition.z - halfWorldHeight;
	zMax = viewingPosition.z + halfWorldHeight;
}

//
// @004c2294 -- erase last frame's footprint and forget it.
//
void
	MapDisplay::EraseDisplay()
{
	previousDrawing.Draw(&localView, backgroundColor);	// FUN_0044a650
	previousDrawing.Clear();							// FUN_0044a630
}

//
// @004c22c4 -- compose the world->view matrix (translate to the viewing
// position, optionally rotate by heading, then scale to pixels), then paint
// the wedge, the static and moving blips and the names, recording the touched
// region for next frame's erase.
//
void
	MapDisplay::DrawDisplay()
{
	AffineMatrix view;
	view.BuildIdentity();				// FUN_0040aadc (twice in decomp)
	view.BuildIdentity();

	//
	// Default the centre to the operator mech if no explicit feed is wired.
	//
	Entity *owner = ResolveOperatorEntity(renderer);
	if (currentPositionPointer == NULL && owner != NULL)
	{
		currentPositionPointer = &EntityPosition(owner);	// owner+0x100
	}

	if (currentPositionPointer != NULL)
	{
		Point3D center = *currentPositionPointer;		// FUN_00408440(local_5c, ...)
		view.SetFromAxis(W_Axis, center);				// FUN_0040b0ac(view,3,center)

		if (currentAngularPointer != NULL)
		{
			if (!rockAndRoll)
			{
				//
				// Top-down: keep heading only.  Extract the yaw, build a
				// yaw-only quaternion and concatenate it.
				//
				EulerAngles euler;
				euler = *currentAngularPointer;						// FUN_0040954c
				Scalar heading = euler.yaw * HeadingHalf;			// * 0.5f
				SinCosPair sc;
				sc = Radian(heading);								// FUN_00408328
				Quaternion yaw(0.0f, sc.sine, 0.0f, sc.cosine);		// FUN_00409948
				view *= yaw;										// FUN_0040adec
			}
			else
			{
				//
				// "Rock and roll": use the full orientation matrix.
				//
				AffineMatrix full;
				full = *currentAngularPointer;						// FUN_00409968
				view *= full;										// FUN_0040adec
			}
		}
	}

	worldToView = view;					// FUN_0040b244(this+0x324, view)

	//
	// Bake the metres->pixels scale into the matrix.
	//
	Vector3D scale;
	scale.x = scale.y = scale.z = pixelsPerMeter;
	worldToView.Multiply(worldToView, scale);				// FUN_0040b374

	BuildRadarShadow(worldToView);							// FUN_004c228c

	flashState = !flashState;								// this+0x374 ^= 1

	localView.AttachRecorder(&previousDrawing);				// (this+0x48 vtbl+0x64)
	DrawViewWedge();										// FUN_004c2484
	DrawStatic(worldToView);								// FUN_004c25b4
	DrawMoving(worldToView);								// FUN_004c2788
	DrawNames(worldToView);									// FUN_004c28c4
	localView.DetachRecorder();								// (this+0x48 vtbl+0x68)
}

//
// @004c228c -- BuildRadarShadow.  In the shipped build this compiled to an
// empty body (the shadow-cast pass is disabled), so the allocated shadowTable
// is never populated.  Reconstructed as a no-op to match the binary.
// (BEST-EFFORT: the intended algorithm -- ray-marching the shadowTable per
// half-degree column -- is not present in the captured object.)
//
void
	MapDisplay::BuildRadarShadow(AffineMatrix & /*worldToView*/)
{
}

//
// @004c2484 -- draw the two edges of the player's field-of-view wedge as lines
// from the map centre out to a far (clipped) radius.
//
void
	MapDisplay::DrawViewWedge()
{
	Radian leftEdge  = viewHorizontalRotation - viewHalfHorizontalWidth;
	Radian rightEdge = viewHorizontalRotation + viewHalfHorizontalWidth;

	localView.SetColor(staticColor);				// (this+0x48 vtbl+0x18)

	Radian edges[2];
	edges[0] = leftEdge;
	edges[1] = rightEdge;

	for (int i = 0; i < 2; ++i)
	{
		Scalar angle = -edges[i];
		SinCosPair sc;
		sc = Radian(angle);							// FUN_00408328
		Scalar x = sc.sine   * ViewWedgeRadius;		// * 10000.0f
		Scalar z = sc.cosine * ViewWedgeRadius;

		localView.MoveToAbsolute(0, 0);				// vtbl+0x24  (centre)
		localView.DrawLineToAbsolute(Round(x), Round(z));	// vtbl+0x30
	}
}

//
// @004c25b4 -- plot every in-range static entity as a blip, plus a name bitmap
// for the special (labelled) ones.
//
void
	MapDisplay::DrawStatic(AffineMatrix &worldToView)
{
	ChainIteratorOf<Entity*> iter(staticEntityList.GetInstanceList());	// FUN_0042ac1c(local_68, this+0x384)
	iter.First();

	PipTable *pips = GetPipTable(renderer);				// *(renderer+0x4C)

	for (;;)
	{
		Entity *entity = iter.ReadAndNext();			// vtbl+0x28
		if (entity == NULL)
		{
			break;
		}

		Vector3D delta;
		delta.Subtract(EntityPosition(entity), viewingPosition);	// FUN_00408644
		if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z > maximumDistanceSquared)
		{
			continue;
		}

		Pip *pip = (pips != NULL)
						? pips->LookUpPip(entity->GetClassID(), 0)	// FUN_004688ce(pips+0x58,entity+0x1bc,0)
						: NULL;
		if (pip != NULL)
		{
			AffineMatrix blipXform;
			AffineMatrix entityXform;
			EntityTransform(&entityXform, entity);
			blipXform.Multiply(entityXform, worldToView);	// FUN_0040b104(local_5c, entity+0xD0, ...)
			pip->Draw(										// FUN_0046f0c0
				LODIndex, metersPerPixel, &localView,
				staticColor, blipXform, 0);
		}

		//
		// Labelled static entities (e.g. nav beacons) also blit a name bitmap.
		//
		if (IsLabelledEntity(entity))	// FUN_0041a1a4 (LabelledEntityClass 0x4e70c4)
		{
			int     nameID  = GetNameID(entity);		// entity+0x21C
			BitMap *bitmap = LookUpNameBitmap(nameID);	// (cache+0x68 vtbl+0x0C)
			if (bitmap != NULL)
			{
				Point3D screen;
				screen.Multiply(EntityPosition(entity), worldToView);	// FUN_00408b98
				localView.MoveToAbsolute(Round(screen.x), Round(screen.z));
				localView.SetColor(MapBlipColor());				// 7
				localView.DrawBitMap(0, bitmap, 0, 0,
					bitmap->Data.Size.x - 1, bitmap->Data.Size.y - 1);
			}
		}
	}
}

//
// @004c2788 -- plot every in-range moving entity (mechs, vehicles) as a blip,
// skipping the operator's own mech and boxing the current target.
//
void
	MapDisplay::DrawMoving(AffineMatrix &worldToView)
{
	ChainIteratorOf<Entity*> iter(movingEntityList.GetInstanceList());	// FUN_0042ac1c(local_1c, this+0x3A0)
	iter.First();

	PipTable *pips  = GetPipTable(renderer);			// *(renderer+0x4C)
	Entity   *owner = ResolveOperatorEntity(renderer);
	Entity   *target = GetTarget(owner);				// owner->GetVideoObject()->target

	for (;;)
	{
		Entity *entity = iter.ReadAndNext();
		if (entity == NULL)
		{
			break;
		}
		if (entity == owner)
		{
			continue;
		}

		Vector3D delta;
		delta.Subtract(EntityPosition(entity), viewingPosition);	// FUN_00408644
		if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z > maximumDistanceSquared)
		{
			continue;
		}

		Pip *pip = (pips != NULL)
						? pips->LookUpPip(entity->GetClassID(), 0)
						: NULL;
		if (pip != NULL)
		{
			AffineMatrix blipXform;
			AffineMatrix entityXform;
			EntityTransform(&entityXform, entity);
			blipXform.Multiply(entityXform, worldToView);	// FUN_0040b104

			int boxColour = (entity == target) ? boxColor : 0;	// highlight target
			pip->Draw(											// FUN_0046f0c0
				LODIndex, metersPerPixel, &localView,
				MapBlipColor() /*7*/, blipXform, boxColour);
		}
	}
}

//
// @004c28c4 -- CONFIRMED (assert path "...BTL4RDR.CPP", line 0x408).
//
// Snapshot up to maximumNames moving entities into the name array, then draw
// their labels.  The hot-box colour flashes (flashState) on the target.
//
void
	MapDisplay::DrawNames(AffineMatrix &worldToView)
{
	Entity *owner = ResolveOperatorEntity(renderer);
	Entity *target = GetTarget(owner);						// *(*(owner+400)+0x284)

	ChainIteratorOf<Entity*> iter(movingEntityList.GetInstanceList());	// FUN_0042ac1c(local_1c, this+0x3A0)
	iter.First();

	int count = iter.GetSize();								// vtbl+0x14
	if (count < 1)
	{
		return;
	}
	if (count > maximumNames)
	{
		count = maximumNames;
	}

	//
	// First pass: extract.
	//
	for (int i = 0; i < count; ++i)
	{
		Entity *entity = iter.ReadAndNext();				// vtbl+0x28
		if (entity == NULL)
		{
			Verify(False);									// FUN_0040385c
														// "MapDisplay::DrawNames -- GetSize() / iterator mismatch"
														// "d:\\tesla\\bt\\bt_l4\\BTL4RDR.CPP", 0x408
		}
		nameArray[i].ExtractFromEntity(entity, worldToView, owner, target);	// FUN_004c19fc
	}

	//
	// Second pass: draw.  The target's label flashes in boxColor.
	//
	int hotboxColor = flashState ? boxColor : 0;
	for (int i = 0; i < count; ++i)
	{
		nameArray[i].Draw(&localView, hotboxColor);		// FUN_004c1ab0
	}
}


//###########################################################################
//###########################################################################
//	BT radar adaptation layer -- implementations
//
//	These reproduce the BT-specific facilities the recovered radar relied on.
//	The WinTesla engine we compile against does not expose them, so they are
//	provided here, best-effort, resolving to "unavailable" until wired to the
//	real BattleTech gauge renderer / video object system (CROSS-FAMILY NEEDS).
//###########################################################################
//###########################################################################

//
// FUN_0046f0c0 / FUN_004688ce -- radar pip symbol + per-model table.
//
void
	Pip::Draw(
		Scalar			/*lod_index*/,
		Scalar			/*meters_per_pixel*/,
		GraphicsView	* /*view*/,
		int				/*color*/,
		AffineMatrix	& /*transform*/,
		int				/*box_color*/
	)
{
	// BEST-EFFORT: the pip raster set lives in the BT renderer's pip table,
	// which is not present in this engine build.
}

Pip *
	PipTable::LookUpPip(int /*model_class_ID*/, int /*variant*/)
{
	return NULL;
}

//
// FUN_00417ab4 -- the gauge renderer's "operator entity" (the mech whose
// cockpit this gauge belongs to).  Not exposed by the WinTesla GaugeRenderer.
//
static Entity *
	ResolveOperatorEntity(GaugeRenderer * /*renderer*/)
{
	return NULL;
}

//
// renderer+0x4C -- the BT pip table.  Not present on the WinTesla renderer.
//
static PipTable *
	GetPipTable(GaugeRenderer * /*renderer*/)
{
	return NULL;
}

//
// *(entity+400) -- the entity's video object (name id + current target).
//
static VideoObject *
	GetVideoObject(Entity * /*entity*/)
{
	return NULL;
}

//
// The global name-bitmap cache (DAT_004efc94 App singleton, +0xC8 graphics).
//
static BitMap *
	LookUpNameBitmap(int /*name_ID*/)
{
	return NULL;
}

//
// entity+0x21C -- a labelled entity's name id.
//
static int
	GetNameID(Entity * /*entity*/)
{
	return -1;
}

//
// FUN_0041a1a4 -- IsDerivedFrom(LabelledEntityClass).
//
static Logical
	IsLabelledEntity(Entity * /*entity*/)
{
	return False;
}

//
// owner->GetVideoObject()->target.
//
static Entity *
	GetTarget(Entity *owner)
{
	VideoObject *video_object = (owner != NULL) ? GetVideoObject(owner) : NULL;
	return (video_object != NULL) ? video_object->target : NULL;
}

//
// FUN_0041f98c -- Entity::FindSubObject(name)  (locates "Torso").
//
static Entity *
	FindSubObject(Entity * /*entity*/, const char * /*name*/)
{
	return NULL;
}

//
// torso+0x1D8 -- the sub-object's horizontal (yaw) rotation feed.
//
static Radian *
	GetHorizontalRotation(Entity * /*entity*/)
{
	return NULL;
}


//###########################################################################
//###########################################################################
//	Local data-source connection helpers (file-private to btl4rdr.cpp)
//
//	The MapDisplay constructor wires "direct-of-pointer" gauge connections so
//	that an externally-owned Point3D*, Quaternion* or Radian is copied into the
//	corresponding MapDisplay member every frame.  In the surviving engine these
//	are the GaugeConnectionDirectOf<T> template (GAUGE.HPP), so the explicit
//	per-type subclasses the decompiler emitted (vtables 00514220 / 00514214 /
//	00514208) are just template instantiations -- no separate code needed.
//###########################################################################
//###########################################################################
