Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
509 lines
13 KiB
C++
509 lines
13 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "audloc.h"
|
|
#include "objstrm.h"
|
|
#include "namelist.h"
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioLocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioLocation::AudioLocation(
|
|
PlugStream *stream,
|
|
Entity *entity
|
|
):
|
|
AudioComponent(stream),
|
|
vectorToSource(0.0f, 0.0f, 0.0f),
|
|
locationOffset(0.0f, 0.0f, 0.0f)
|
|
{
|
|
AudioFrameCount next_update_delay;
|
|
Scalar clipping_scale;
|
|
|
|
MemoryStream_Read(stream, &locationOffset);
|
|
MemoryStream_Read(stream, &next_update_delay);
|
|
MemoryStream_Read(stream, &clipping_scale);
|
|
|
|
AudioLocationX(entity, next_update_delay, clipping_scale);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
AudioLocation::~AudioLocation()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioLocation::AudioLocationX(
|
|
Entity *entity,
|
|
AudioFrameCount next_update_delay,
|
|
Scalar clipping_scale
|
|
)
|
|
{
|
|
Check(entity);
|
|
linkedEntity = entity;
|
|
entity->AddAudioComponent(this);
|
|
|
|
clippingScale = clipping_scale;
|
|
isHeadSource = False;
|
|
distanceToSource = 1.0f;
|
|
dopplerCents = 0;
|
|
#if 0
|
|
angleOffOrientation = 0.0f;
|
|
#endif
|
|
azimuthOfSource = 0.0f;
|
|
distanceVolumeScale = 1.0f;
|
|
highFreqCutoffScale = 1.0f;
|
|
#if 0
|
|
reverbVolumeScale = 0.0f;
|
|
#endif
|
|
nextUpdateDelay = next_update_delay;
|
|
nextUpdateFrame = NullAudioFrameCount;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioLocation::TestInstance() const
|
|
{
|
|
Component::TestInstance();
|
|
return True;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioLocation::BuildFromPage(
|
|
PlugStream *stream,
|
|
NameList *name_list,
|
|
ClassID class_ID,
|
|
ObjectID object_ID
|
|
)
|
|
{
|
|
AudioComponent::BuildFromPage(stream, name_list, class_ID, object_ID);
|
|
|
|
MEM_STRM_WRITE_ENTRY(*stream, name_list, Point3D, location_offset);
|
|
|
|
//
|
|
// Read update delay
|
|
//
|
|
CString update_delay_string("update_delay");
|
|
AudioFrameCount update_delay = DefaultAudioFrameDelay;
|
|
|
|
if (name_list->FindData(update_delay_string) != NULL)
|
|
{
|
|
Check_Pointer(name_list->FindData(update_delay_string));
|
|
Convert_From_Ascii(
|
|
(const char *)name_list->FindData(update_delay_string),
|
|
&update_delay
|
|
);
|
|
}
|
|
MemoryStream_Write(stream, &update_delay);
|
|
|
|
//
|
|
// Read culling scale
|
|
//
|
|
CString clipping_scale_string("clipping_scale");
|
|
Scalar clipping_scale = 1.0f;
|
|
|
|
if (name_list->FindData(clipping_scale_string) != NULL)
|
|
{
|
|
Check_Pointer(name_list->FindData(clipping_scale_string));
|
|
Convert_From_Ascii(
|
|
(const char *)name_list->FindData(clipping_scale_string),
|
|
&clipping_scale
|
|
);
|
|
}
|
|
MemoryStream_Write(stream, &clipping_scale);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioLocation::ReceiveControl(
|
|
AudioControlID,
|
|
AudioControlValue
|
|
)
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioLocation::IsAudioLocationClipped(AudioHead *audio_head)
|
|
{
|
|
Check(this);
|
|
Check(audio_head);
|
|
|
|
//
|
|
// HACK - This is a rough approximation in that it does not
|
|
// take into account the offset of the location from the
|
|
// center of the entity. But, this is more efficient and is OK
|
|
// as long as the clipping sphere does not exclude the location
|
|
// unnaturally
|
|
//
|
|
Entity
|
|
*head_entity;
|
|
Vector3D
|
|
difference;
|
|
Scalar
|
|
scaled_radius;
|
|
|
|
head_entity = audio_head->GetHeadEntity();
|
|
Check(head_entity);
|
|
Check(linkedEntity);
|
|
difference.Subtract(
|
|
head_entity->localOrigin.linearPosition,
|
|
linkedEntity->localOrigin.linearPosition
|
|
);
|
|
scaled_radius = audio_head->GetClippingRadius() * clippingScale;
|
|
return difference.LengthSquared() > scaled_radius * scaled_radius;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
AudioLocation::UpdateSpatialModelImplementation(AudioHead *audio_head)
|
|
{
|
|
Check(this);
|
|
Check(audio_head);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get head and source entity
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Entity *source_entity = GetLinkedEntity();
|
|
Entity *head_entity = audio_head->GetHeadEntity();
|
|
|
|
Check(source_entity);
|
|
Check(head_entity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Catch case of head == source at origin
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
head_entity == source_entity &&
|
|
locationOffset == Vector3D::Identity
|
|
)
|
|
{
|
|
isHeadSource = True;
|
|
vectorToSource = Vector3D::Identity;
|
|
distanceToSource = 0.0f;
|
|
dopplerCents = 0;
|
|
#if 0
|
|
angleOffOrientation = 0.0f;
|
|
#endif
|
|
azimuthOfSource = 0.0f;
|
|
distanceVolumeScale = 1.0f;
|
|
highFreqCutoffScale = 1.0f;
|
|
#if 0
|
|
reverbVolumeScale = audio_head->GetReverbToDryRatio();
|
|
#endif
|
|
return;
|
|
}
|
|
isHeadSource = False;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the ear to world linear matrix
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
LinearMatrix ear_to_world = audio_head->GetEarToWorld();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate vector to source with respect to local coordinate system
|
|
// of the ears
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
{
|
|
//
|
|
// Get the source location offset from the entity position in
|
|
// world coordinates
|
|
//
|
|
Vector3D world_location_offset;
|
|
Vector3D world_source_location;
|
|
|
|
world_location_offset.Multiply(
|
|
locationOffset,
|
|
source_entity->localToWorld
|
|
);
|
|
Check(&world_location_offset);
|
|
|
|
world_source_location.Add(
|
|
source_entity->localOrigin.linearPosition,
|
|
world_location_offset
|
|
);
|
|
Check(&world_source_location);
|
|
|
|
//
|
|
// Get the vector from the ear to the source, note that the vector
|
|
// is not calculated by the ears offset but from the entities location
|
|
//
|
|
Vector3D world_ear_to_source_vector;
|
|
|
|
world_ear_to_source_vector.Subtract(
|
|
world_source_location,
|
|
head_entity->localOrigin.linearPosition
|
|
);
|
|
Check(&world_ear_to_source_vector);
|
|
|
|
//
|
|
// Transform the vector into ear coordinates
|
|
//
|
|
vectorToSource.MultiplyByInverse(
|
|
world_ear_to_source_vector,
|
|
ear_to_world
|
|
);
|
|
Check(&vectorToSource);
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate distance to source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
distanceToSource = vectorToSource.Length();
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate normal to source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Vector3D normal_to_source(0.0f, 0.0f, -1.0f);
|
|
|
|
if (!Small_Enough(distanceToSource))
|
|
{
|
|
normal_to_source.Normalize(vectorToSource);
|
|
Check(&normal_to_source);
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate angle between the orientation of the head and source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (Small_Enough(distanceToSource))
|
|
{
|
|
angleOffOrientation = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
UnitVector direction;
|
|
Scalar cosine;
|
|
|
|
ear_to_world.GetToAxis(Z_Axis, &direction);
|
|
Check(&direction);
|
|
|
|
Check(&normal_to_source);
|
|
cosine = normal_to_source * direction;
|
|
Clamp(cosine, -1.0f, 1.0f);
|
|
angleOffOrientation = Arccos(cosine);
|
|
angleOffOrientation.Normalize();
|
|
}
|
|
Verify(
|
|
angleOffOrientation <= 180.0f*RAD_PER_DEG &&
|
|
angleOffOrientation >= -180.0f*RAD_PER_DEG
|
|
);
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate azimuth
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z))
|
|
{
|
|
azimuthOfSource = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
Verify(!(Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z)));
|
|
azimuthOfSource = Arctan(vectorToSource.x, vectorToSource.z);
|
|
}
|
|
Verify(
|
|
azimuthOfSource <= 180.0f*RAD_PER_DEG &&
|
|
azimuthOfSource >= -180.0f*RAD_PER_DEG
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate distance volume scaling
|
|
// Apply clipping scale to rolloff distance scale, thereby giving this
|
|
// audio location a specifc rolloff characteristic
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
|
|
{
|
|
//
|
|
// y = 1 / 1 + (K(x - knee))^exp
|
|
//
|
|
Verify(!Small_Enough(clippingScale));
|
|
const Scalar temp =
|
|
(audio_head->GetAmplitudeRollOffDistanceScale() / clippingScale) *
|
|
(distanceToSource - audio_head->GetAmplitudeRollOffKnee());
|
|
const Scalar temp2 =
|
|
1.0f + pow(temp, audio_head->GetAmplitudeRollOffExponent());
|
|
|
|
Verify(!Small_Enough(temp2));
|
|
distanceVolumeScale = 1.0f / temp2;
|
|
}
|
|
else
|
|
{
|
|
distanceVolumeScale = 1.0f;
|
|
}
|
|
Verify(distanceVolumeScale >= 0.0f && distanceVolumeScale <= 1.0f);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate high frequency cutoff scaling
|
|
// Apply clipping scale to high frequency rolloff distance scale, thereby
|
|
// giving this audio location a specifc rolloff characteristic
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (distanceToSource > audio_head->GetHighFrequencyRollOffKnee())
|
|
{
|
|
//
|
|
// y = 1 / 1 + (K(x - knee))^exp
|
|
//
|
|
Verify(!Small_Enough(clippingScale));
|
|
const Scalar temp =
|
|
(audio_head->GetHighFrequencyRollOffDistanceScale() / clippingScale) *
|
|
(distanceToSource - audio_head->GetHighFrequencyRollOffKnee());
|
|
const Scalar temp2 =
|
|
1.0f + pow(temp, audio_head->GetHighFrequencyRollOffExponent());
|
|
|
|
Verify(!Small_Enough(temp2));
|
|
highFreqCutoffScale = 1.0f / temp2;
|
|
}
|
|
else
|
|
{
|
|
highFreqCutoffScale = 1.0f;
|
|
}
|
|
Verify(highFreqCutoffScale >= 0.0f && highFreqCutoffScale <= 1.0f);
|
|
|
|
#if 0
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate reverb volume scaling
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
|
|
{
|
|
const Scalar temp =
|
|
distanceToSource - audio_head->GetAmplitudeRollOffKnee() + 1.0f;
|
|
|
|
reverbVolumeScale =
|
|
audio_head->GetReverbToDryRatio() *
|
|
pow(temp, audio_head->GetAmplitudeRollOffExponent());
|
|
if (reverbVolumeScale > 1.0f)
|
|
reverbVolumeScale = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
reverbVolumeScale = audio_head->GetReverbToDryRatio();
|
|
}
|
|
Verify(reverbVolumeScale >= 0.0f && reverbVolumeScale <= 1.0f);
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate relative velocity of source
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Vector3D relative_velocity_temp;
|
|
Vector3D relative_velocity;
|
|
|
|
relative_velocity_temp.Subtract(
|
|
source_entity->GetWorldLinearVelocity(),
|
|
head_entity->GetWorldLinearVelocity()
|
|
);
|
|
Check(&relative_velocity_temp);
|
|
|
|
relative_velocity.MultiplyByInverse(
|
|
relative_velocity_temp,
|
|
head_entity->localToWorld
|
|
);
|
|
Check(&relative_velocity);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Calculate doppler shift in cents
|
|
// cents = (c / (c - v)) / ScaleRatio
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!Small_Enough(distanceToSource))
|
|
{
|
|
AudioPitchCents
|
|
cents;
|
|
Scalar speed_of_source =
|
|
normal_to_source * relative_velocity;
|
|
const Scalar ear_radius =
|
|
audio_head->GetDistanceBetweenEars() * 0.5f;
|
|
|
|
if (distanceToSource < ear_radius)
|
|
{
|
|
Verify(!Small_Enough(ear_radius));
|
|
speed_of_source *= (distanceToSource / ear_radius);
|
|
}
|
|
|
|
if (CalculateDoppler(audio_head, speed_of_source, ¢s))
|
|
{
|
|
dopplerCents = cents;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dopplerCents = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
Logical
|
|
AudioLocation::CalculateDoppler(
|
|
AudioHead *audio_head,
|
|
Scalar speed,
|
|
AudioPitchCents *result
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(audio_head);
|
|
Check_Pointer(result);
|
|
|
|
Scalar speed_of_sound = audio_head->GetAudioSoundSpeed();
|
|
|
|
Clamp(speed, -speed_of_sound, speed_of_sound);
|
|
if (!Small_Enough(speed_of_sound - speed))
|
|
{
|
|
*result = audio_head->GetAudioDopplerConstant() *
|
|
( 1 - speed_of_sound / (speed_of_sound - speed) );
|
|
return True;
|
|
}
|
|
return False;
|
|
}
|