BT410 5.3.110: the gyroscope lives -- the cockpit's springs, the hit bounce, and the crunch, all wired
The feel wave's cornerstone, transcribed from the task-#56 byte-exact donor
per the GYRO.NOTES.md plan:
THE SPRINGS. IntegrateEyeJoint (@004b2ec0): the eye TRANSLATION spring
whose equilibrium (posSpring+negSpring)/2 IS the authentic steady eye
offset, with the byte-verified quirks intact -- the damping step
OVERWRITES the force accumulator (it carries last frame's damping and
the damage impulses by design) and the position step has NO dt.
IntegrateBody (@004b30ec): the body ROTATION spring, X/Z-crossed on the
spring force and crossed AGAIN on the orientation step.
THE SIM (@004b275c): chain the power watch (5.3.109's mirrors), slew the
sway angle between the powered and impaired percentages -- impaired reads
the gyro's OWN mirrored alarms: watchdog != Ready or heat == Failure --
band-clamp, run both integrators. The Performance ends there.
THE JOINT WRITES (@004b33e0/@004b34ec) live where the binary calls them:
the MECH master-perf tail after the gait pass, death-gated -- the idle
sway onto the EyeJoint, and TRANSLATION <- eyePosition + ROTATION <-
bodyOrientation onto 'jointeye', the BallTranslation joint the cockpit
eyepoint rides. Every write QuantiseEps-gated.
THE HIT BOUNCE (@004b2980): every non-collision hit shakes the cockpit.
Direction from the damage force (random horizontal when ~zero), rotated
into the yaw-only torso frame, per-type priced amount/multiplier*response
(Explosive alone reads burstCount), clamped 1.3 UPPER only, then the four
kicks -- impulse, torque (which zeroes .y and negates itself), the fixed
up-axis vibration, and the vertical mirror. Fed from the damage hub's
step 1, BEFORE zone resolution -- an invalid-zone hit still shakes you.
THE CRUNCH: driving through a crushable prop now kicks the gyro (torque
0.4 along the contact normal, upward impulse 0.2) -- the 5.3.102 staged
site, closed.
Resource: the five DamageResponse quads {trans,pitchRoll,yaw,vibration}
appended after the multipliers (record 0x21C, binary-verified layout) --
they were always in the stream; we simply never read them. The ctor now
zeroes every accumulator (the donor's history records a NaN poison from
exactly that omission), copies the clamp pairs (body doubled), self-points
externalPitchPtr at spare0 until a torso binds it, and REGISTERS the
Performance on the master -- it ticks live, no fault.
Stub census: 19 across 13 files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
# pod_render_norio.conf -- pod_render_rec with the RIO serial port OFF.
|
||||||
|
#
|
||||||
|
# The emulator log shows a steady stream of serial1 RX OVERRUN errors on
|
||||||
|
# the RIO pipe, and controls post their events at HighEventPriority
|
||||||
|
# UNCONDITIONALLY (CONTROLS.HPP:250) -- so a chattering RIO port would
|
||||||
|
# flood a priority the background pump always serves first, starving the
|
||||||
|
# priority-0 renderer events the load gate waits on. This conf tests that
|
||||||
|
# by removing the port entirely. Everything else is identical to the rec
|
||||||
|
# conf, so a launch here and a hang there isolates the RIO.
|
||||||
|
#
|
||||||
|
[sdl]
|
||||||
|
output=opengl
|
||||||
|
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
|
||||||
|
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
|
||||||
|
priority=higher,higher
|
||||||
|
[dosbox]
|
||||||
|
memsize=32
|
||||||
|
machine=svga_s3
|
||||||
|
[cpu]
|
||||||
|
core=dynamic
|
||||||
|
cputype=pentium
|
||||||
|
cycles=max
|
||||||
|
[sblaster]
|
||||||
|
sbtype=sb16
|
||||||
|
sbbase=220
|
||||||
|
irq=5
|
||||||
|
dma=1
|
||||||
|
hdma=5
|
||||||
|
[mixer]
|
||||||
|
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
|
||||||
|
# emulation-thread stalls (RIO retry recovery) don't audibly chop
|
||||||
|
rate=44100
|
||||||
|
blocksize=1024
|
||||||
|
prebuffer=60
|
||||||
|
[serial]
|
||||||
|
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
|
||||||
|
# few-ms ACK deadline is met; plasma display on COM2 (real pod has both).
|
||||||
|
# VWE fork namedpipe backend (com0com/realport retired -- COM1/COM2 gone):
|
||||||
|
# DOSBox = pipe client (retry), vRIO/vPLASMA apps = servers; an unconnected
|
||||||
|
# pipe behaves as an unplugged cable so the mission still runs. serialnamedpipe.h
|
||||||
|
serial1=disabled
|
||||||
|
serial2=namedpipe pipe:vplasma
|
||||||
|
# live UNBUFFERED game output: DOS char devices are not buffered, so
|
||||||
|
# redirecting stdout to COM3 lands every line immediately. A normal
|
||||||
|
# '> file' redirect stays 0 bytes until the process exits, which hides
|
||||||
|
# all progress on a run that does NOT crash.
|
||||||
|
serial3=file file:C:\VWE\TeslaRel410\emulator\render-bridge\podlog.txt
|
||||||
|
[autoexec]
|
||||||
|
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||||
|
c:
|
||||||
|
cd \REL410\BT
|
||||||
|
set VIDEOFORMAT=svga
|
||||||
|
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
|
||||||
|
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
|
||||||
|
rem driver relies on; skipping it left the cards uninitialized (silent).
|
||||||
|
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
|
||||||
|
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
|
||||||
|
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
|
||||||
|
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||||
|
c:\sb16\diagnose /s
|
||||||
|
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||||
|
c:\sb16\diagnose /s
|
||||||
|
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||||
|
set TEMP=c:\
|
||||||
|
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
|
||||||
|
rem attached; stdout redirected so mission-load progress survives kills.
|
||||||
|
set BT_JOINTS=1
|
||||||
|
set L4VIEWEXT=1
|
||||||
|
set BT_MECH_LOG=1
|
||||||
|
set BT_GYRO_LOG=1
|
||||||
|
set BT_MAP_LOG=1
|
||||||
|
set BT_MER_LOG=1
|
||||||
|
set BT_VID_LOG=1
|
||||||
|
set BT_LAUNCH_LOG=1
|
||||||
|
set BT_STACK_LOG=1
|
||||||
|
set BT_FORCE_THROTTLE=0.6
|
||||||
|
set BT_FORCE_TURN=0.25
|
||||||
|
set HEAPSIZE=15000000
|
||||||
|
set L4GAUGE=640x480x16
|
||||||
|
call setenv.bat r s n p
|
||||||
|
32rtm.exe -x
|
||||||
|
BTL4REC.EXE -egg testarn.egg > COM3
|
||||||
|
|
||||||
|
echo GAME-RC=%errorlevel% >> RC.TXT
|
||||||
|
32rtm.exe -u
|
||||||
|
echo ALPHA1-RUN-DONE
|
||||||
|
pause
|
||||||
|
|
||||||
+603
-117
@@ -1,117 +1,603 @@
|
|||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
// File: gyro.cpp //
|
// File: gyro.cpp //
|
||||||
// Project: BattleTech Brick: Mech subsystems //
|
// Project: BattleTech Brick: Mech subsystems //
|
||||||
// Contents: Gyroscope -- eye/body stabilisation //
|
// Contents: Gyroscope -- eye/body stabilisation //
|
||||||
//---------------------------------------------------------------------------//
|
//---------------------------------------------------------------------------//
|
||||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||||
// All Rights reserved worldwide //
|
// All Rights reserved worldwide //
|
||||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
|
|
||||||
#include <bt.hpp>
|
#include <bt.hpp>
|
||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
#if !defined(GYRO_HPP)
|
#if !defined(GYRO_HPP)
|
||||||
# include <gyro.hpp>
|
# include <gyro.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(MECH_HPP)
|
#if !defined(MECH_HPP)
|
||||||
# include <mech.hpp>
|
# include <mech.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Derivation
|
#if !defined(RANDOM_HPP)
|
||||||
Gyroscope::ClassDerivations(
|
# include <random.hpp>
|
||||||
PowerWatcher::ClassDerivations,
|
#endif
|
||||||
"Gyroscope"
|
|
||||||
);
|
Derivation
|
||||||
|
Gyroscope::ClassDerivations(
|
||||||
Gyroscope::SharedData
|
PowerWatcher::ClassDerivations,
|
||||||
Gyroscope::DefaultData(
|
"Gyroscope"
|
||||||
Gyroscope::ClassDerivations,
|
);
|
||||||
Subsystem::MessageHandlers,
|
|
||||||
Subsystem::AttributeIndex,
|
Gyroscope::SharedData
|
||||||
Subsystem::StateCount
|
Gyroscope::DefaultData(
|
||||||
);
|
Gyroscope::ClassDerivations,
|
||||||
|
Subsystem::MessageHandlers,
|
||||||
Gyroscope::Gyroscope(
|
Subsystem::AttributeIndex,
|
||||||
Mech *owner,
|
Subsystem::StateCount
|
||||||
int subsystem_ID,
|
);
|
||||||
SubsystemResource *r,
|
|
||||||
SharedData &shared_data
|
Gyroscope::Gyroscope(
|
||||||
):
|
Mech *owner,
|
||||||
PowerWatcher(owner, subsystem_ID, r, shared_data)
|
int subsystem_ID,
|
||||||
{
|
SubsystemResource *r,
|
||||||
Check(owner);
|
SharedData &shared_data
|
||||||
Check_Pointer(r);
|
):
|
||||||
|
PowerWatcher(owner, subsystem_ID, r, shared_data)
|
||||||
exageration = r->exageration;
|
{
|
||||||
maxAnimationNoise = r->maxAnimationNoise;
|
Check(owner);
|
||||||
minAnimationNoise = r->minAnimationNoise;
|
Check_Pointer(r);
|
||||||
rotationPerSecond = r->rotationPerSecond;
|
|
||||||
percentageOnNormal = r->percentageOnNormal;
|
exageration = r->exageration;
|
||||||
percentageOnDestruction = r->percentageOnDestruction;
|
maxAnimationNoise = r->maxAnimationNoise;
|
||||||
percentageOnDegradation = r->percentageOnDegradation;
|
minAnimationNoise = r->minAnimationNoise;
|
||||||
percentageOnFailure = r->percentageOnFailure;
|
rotationPerSecond = r->rotationPerSecond;
|
||||||
|
percentageOnNormal = r->percentageOnNormal;
|
||||||
springConstant = r->springConstant;
|
percentageOnDestruction = r->percentageOnDestruction;
|
||||||
dampingConstant = r->dampingConstant;
|
percentageOnDegradation = r->percentageOnDegradation;
|
||||||
posSpring = r->posSpring;
|
percentageOnFailure = r->percentageOnFailure;
|
||||||
negSpring = r->negSpring;
|
|
||||||
rotationSpringConstant = r->rotationSpringConstant;
|
springConstant = r->springConstant;
|
||||||
rotationDampingConstant = r->rotationDampingConstant;
|
dampingConstant = r->dampingConstant;
|
||||||
rotationPosSpring = r->rotationPosSpring;
|
posSpring = r->posSpring;
|
||||||
rotationNegSpring = r->rotationNegSpring;
|
negSpring = r->negSpring;
|
||||||
|
rotationSpringConstant = r->rotationSpringConstant;
|
||||||
damageMultiplier[0] = r->collisionDamageMultiplier;
|
rotationDampingConstant = r->rotationDampingConstant;
|
||||||
damageMultiplier[1] = r->ballisticDamageMultiplier;
|
rotationPosSpring = r->rotationPosSpring;
|
||||||
damageMultiplier[2] = r->explosiveDamageMultiplier;
|
rotationNegSpring = r->rotationNegSpring;
|
||||||
damageMultiplier[3] = r->laserDamageMultiplier;
|
|
||||||
damageMultiplier[4] = r->energyDamageMultiplier;
|
damageMultiplier[0] = r->collisionDamageMultiplier;
|
||||||
|
damageMultiplier[1] = r->ballisticDamageMultiplier;
|
||||||
//
|
damageMultiplier[2] = r->explosiveDamageMultiplier;
|
||||||
// Zero the eye/body dynamics accumulators (the binary zeroes each; the
|
damageMultiplier[3] = r->laserDamageMultiplier;
|
||||||
// per-frame sim seeds them from the springs above).
|
damageMultiplier[4] = r->energyDamageMultiplier;
|
||||||
//
|
|
||||||
for (int i = 0; i < 64; ++i)
|
damageResponse[0] = r->collisionDamageResponse;
|
||||||
{
|
damageResponse[1] = r->ballisticDamageResponse;
|
||||||
dynamicsState[i] = 0.0f;
|
damageResponse[2] = r->explosiveDamageResponse;
|
||||||
}
|
damageResponse[3] = r->laserDamageResponse;
|
||||||
|
damageResponse[4] = r->energyDamageResponse;
|
||||||
Check_Fpu();
|
|
||||||
}
|
//
|
||||||
|
// The clamps are ctor-time COPIES of the spring pairs; the body pair is
|
||||||
Gyroscope::~Gyroscope()
|
// doubled. Every accumulator zeroes -- the binary leaves nothing to
|
||||||
{
|
// allocator fill (the donor's history records a NaN poison from exactly
|
||||||
}
|
// that omission).
|
||||||
|
//
|
||||||
Logical
|
eyeClampUpper = posSpring;
|
||||||
Gyroscope::TestClass(Mech &)
|
eyeClampLower = negSpring;
|
||||||
{
|
bodyClampUpper.Multiply(rotationPosSpring, 2.0f);
|
||||||
return True;
|
bodyClampLower.Multiply(rotationNegSpring, 2.0f);
|
||||||
}
|
|
||||||
|
{
|
||||||
Logical
|
Vector3D
|
||||||
Gyroscope::TestInstance() const
|
vzero(0.0f, 0.0f, 0.0f);
|
||||||
{
|
eyePosition = eyeForce = eyeVelocity = eyeWork = vzero;
|
||||||
return IsDerivedFrom(ClassDerivations);
|
bodyOrientation = bodyForce = bodyVelocity = bodyWork = vzero;
|
||||||
}
|
placePos = placeRot = vzero;
|
||||||
|
placeQuat[0] = placeQuat[1] = placeQuat[2] = 0.0f;
|
||||||
void
|
placeQuat[3] = 1.0f;
|
||||||
Gyroscope::ResetToInitialState()
|
|
||||||
{
|
int i;
|
||||||
Check(this);
|
for (i = 0; i < 12; ++i)
|
||||||
for (int i = 0; i < 64; ++i)
|
{
|
||||||
{
|
workMatrix[i] = 0.0f;
|
||||||
dynamicsState[i] = 0.0f;
|
}
|
||||||
}
|
workMatrix[0] = workMatrix[5] = workMatrix[10] = 1.0f;
|
||||||
}
|
|
||||||
|
spare0 = 0.0f;
|
||||||
//
|
externalPitchPtr = &spare0;
|
||||||
// Per-frame eye/body stabilisation (spring/damper integration driving the
|
}
|
||||||
// cockpit eye joint). Not yet reconstructed.
|
vibrationDirection = Vector3D(0.0f, 1.0f, 0.0f);
|
||||||
//
|
swayAngle = 0.0f;
|
||||||
void
|
swayVelocity = 0.0f;
|
||||||
Gyroscope::GyroscopeSimulation(Scalar)
|
swayActive = 0;
|
||||||
{
|
swayBias = 0.0f;
|
||||||
Fail("Gyroscope::GyroscopeSimulation -- gyro.cpp not yet reconstructed");
|
|
||||||
}
|
eyeJointNode = owner->ResolveJoint(r->eyeJoint);
|
||||||
|
mechJointNode = owner->ResolveJoint(r->mechJoint);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The master instance runs the gyro per-frame.
|
||||||
|
//
|
||||||
|
if (owner->GetInstance() != Entity::ReplicantInstance)
|
||||||
|
{
|
||||||
|
SetPerformance(&Gyroscope::GyroscopeSimulation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Check_Fpu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gyroscope::~Gyroscope()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
Gyroscope::TestClass(Mech &)
|
||||||
|
{
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logical
|
||||||
|
Gyroscope::TestInstance() const
|
||||||
|
{
|
||||||
|
return IsDerivedFrom(ClassDerivations);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Gyroscope::ResetToInitialState()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
{
|
||||||
|
Vector3D
|
||||||
|
vzero(0.0f, 0.0f, 0.0f);
|
||||||
|
eyePosition = eyeForce = eyeVelocity = eyeWork = vzero;
|
||||||
|
bodyOrientation = bodyForce = bodyVelocity = bodyWork = vzero;
|
||||||
|
swayAngle = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// @004b275c -- the per-frame gyro. Chain the power watch, slew the sway
|
||||||
|
// angle toward the powered or impaired percentage, and run both spring
|
||||||
|
// integrators. THE PERFORMANCE ENDS THERE: the joint writes are called from
|
||||||
|
// the MECH master performance tail after the gait, not from here.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Gyroscope::GyroscopeSimulation(Scalar time_slice)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
PowerWatcher::Simulation(time_slice);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The impaired test reads the gyro's OWN mirrored alarms -- the two
|
||||||
|
// UpdateWatch just drove: the voltage watchdog and the heat watch.
|
||||||
|
//
|
||||||
|
Logical
|
||||||
|
impaired =
|
||||||
|
watchdogAlarm.GetLevel() != PoweredSubsystem::Ready ||
|
||||||
|
heatAlarm.GetLevel() == HeatSink::FailureHeat;
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
target =
|
||||||
|
(impaired ? percentageOnDestruction : percentageOnNormal)
|
||||||
|
+ swayBias;
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
step = rotationPerSecond * time_slice;
|
||||||
|
if (target < swayAngle)
|
||||||
|
{
|
||||||
|
step = -step;
|
||||||
|
}
|
||||||
|
swayAngle += step;
|
||||||
|
|
||||||
|
if (step <= 0.0f)
|
||||||
|
{
|
||||||
|
if (step < 0.0f && swayAngle < target)
|
||||||
|
{
|
||||||
|
swayAngle = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (swayAngle > target)
|
||||||
|
{
|
||||||
|
swayAngle = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swayAngle > maxAnimationNoise)
|
||||||
|
{
|
||||||
|
swayAngle = maxAnimationNoise;
|
||||||
|
}
|
||||||
|
if (swayAngle < minAnimationNoise)
|
||||||
|
{
|
||||||
|
swayAngle = minAnimationNoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrateEyeJoint(time_slice);
|
||||||
|
IntegrateBody(time_slice);
|
||||||
|
|
||||||
|
Check_Fpu();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// @004b2ec0 -- the eye TRANSLATION spring, byte-exact quirks preserved: both
|
||||||
|
// spring terms use springConstant; the damping step OVERWRITES the force
|
||||||
|
// accumulator (it carries last frame's damping + damage impulses by design);
|
||||||
|
// and the position step has NO dt. Equilibrium is (posSpring+negSpring)/2
|
||||||
|
// per axis -- the authentic steady eye offset.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Gyroscope::IntegrateEyeJoint(Scalar time_slice)
|
||||||
|
{
|
||||||
|
Vector3D
|
||||||
|
toward_negative,
|
||||||
|
toward_positive,
|
||||||
|
force;
|
||||||
|
|
||||||
|
toward_negative.Subtract(eyePosition, negSpring);
|
||||||
|
toward_positive.Subtract(eyePosition, posSpring);
|
||||||
|
|
||||||
|
force.Multiply(springConstant, toward_negative);
|
||||||
|
eyeForce += force;
|
||||||
|
force.Multiply(springConstant, toward_positive);
|
||||||
|
eyeForce += force;
|
||||||
|
|
||||||
|
eyeWork = eyeForce;
|
||||||
|
force.Multiply(eyeWork, time_slice);
|
||||||
|
eyeVelocity += force;
|
||||||
|
|
||||||
|
eyeForce.Multiply(dampingConstant, eyeVelocity);
|
||||||
|
eyeWork = eyeForce;
|
||||||
|
force.Multiply(eyeWork, time_slice);
|
||||||
|
eyeVelocity += force;
|
||||||
|
|
||||||
|
eyePosition += eyeVelocity;
|
||||||
|
|
||||||
|
if (eyePosition.x > eyeClampUpper.x) eyePosition.x = eyeClampUpper.x;
|
||||||
|
if (eyePosition.y > eyeClampUpper.y) eyePosition.y = eyeClampUpper.y;
|
||||||
|
if (eyePosition.z > eyeClampUpper.z) eyePosition.z = eyeClampUpper.z;
|
||||||
|
if (eyePosition.x < eyeClampLower.x) eyePosition.x = eyeClampLower.x;
|
||||||
|
if (eyePosition.y < eyeClampLower.y) eyePosition.y = eyeClampLower.y;
|
||||||
|
if (eyePosition.z < eyeClampLower.z) eyePosition.z = eyeClampLower.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// @004b30ec -- the body ROTATION spring. Same shape with three verified
|
||||||
|
// quirks: the spring force is X/Z-CROSSED (k.x*d.z, k.y*d.y, k.z*d.x); the
|
||||||
|
// damping is componentwise UNcrossed; and the orientation step crosses AGAIN
|
||||||
|
// (x += v.z, z += v.x).
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Gyroscope::IntegrateBody(Scalar time_slice)
|
||||||
|
{
|
||||||
|
Vector3D
|
||||||
|
toward_negative,
|
||||||
|
toward_positive,
|
||||||
|
force;
|
||||||
|
|
||||||
|
toward_negative.Subtract(bodyOrientation, rotationNegSpring);
|
||||||
|
toward_positive.Subtract(bodyOrientation, rotationPosSpring);
|
||||||
|
|
||||||
|
force.x = rotationSpringConstant.x * toward_negative.z;
|
||||||
|
force.y = rotationSpringConstant.y * toward_negative.y;
|
||||||
|
force.z = rotationSpringConstant.z * toward_negative.x;
|
||||||
|
bodyForce += force;
|
||||||
|
force.x = rotationSpringConstant.x * toward_positive.z;
|
||||||
|
force.y = rotationSpringConstant.y * toward_positive.y;
|
||||||
|
force.z = rotationSpringConstant.z * toward_positive.x;
|
||||||
|
bodyForce += force;
|
||||||
|
|
||||||
|
bodyWork = bodyForce;
|
||||||
|
force.Multiply(bodyWork, time_slice);
|
||||||
|
bodyVelocity += force;
|
||||||
|
|
||||||
|
bodyForce.Multiply(rotationDampingConstant, bodyVelocity);
|
||||||
|
bodyWork = bodyForce;
|
||||||
|
force.Multiply(bodyWork, time_slice);
|
||||||
|
bodyVelocity += force;
|
||||||
|
|
||||||
|
bodyOrientation.x += bodyVelocity.z;
|
||||||
|
bodyOrientation.y += bodyVelocity.y;
|
||||||
|
bodyOrientation.z += bodyVelocity.x;
|
||||||
|
|
||||||
|
if (bodyOrientation.y > bodyClampUpper.y) bodyOrientation.y = bodyClampUpper.y;
|
||||||
|
if (bodyOrientation.x > bodyClampUpper.x) bodyOrientation.x = bodyClampUpper.x;
|
||||||
|
if (bodyOrientation.z > bodyClampUpper.z) bodyOrientation.z = bodyClampUpper.z;
|
||||||
|
if (bodyOrientation.y < bodyClampLower.y) bodyOrientation.y = bodyClampLower.y;
|
||||||
|
if (bodyOrientation.x < bodyClampLower.x) bodyOrientation.x = bodyClampLower.x;
|
||||||
|
if (bodyOrientation.z < bodyClampLower.z) bodyOrientation.z = bodyClampLower.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// @004b33e0 / @004b34ec -- the joint writes, called from the MECH master
|
||||||
|
// performance tail after the gait pass.
|
||||||
|
//
|
||||||
|
// WriteEyeJoint scales the EyeJoint's own channel by swayAngle -- the idle
|
||||||
|
// wander. WriteMechJoint pushes the integrated state onto 'jointeye'
|
||||||
|
// (BallTranslation ONLY): TRANSLATION <- eyePosition, ROTATION <-
|
||||||
|
// bodyOrientation, each write gated on a real change.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
|
||||||
|
static const Scalar
|
||||||
|
GyroQuantiseEps = 0.0001f;
|
||||||
|
|
||||||
|
void
|
||||||
|
Gyroscope::WriteEyeJoint()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
if (eyeJointNode == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (eyeJointNode->GetJointType())
|
||||||
|
{
|
||||||
|
case Joint::HingeXJointType:
|
||||||
|
case Joint::HingeYJointType:
|
||||||
|
case Joint::HingeZJointType:
|
||||||
|
{
|
||||||
|
Scalar
|
||||||
|
base = (Scalar)eyeJointNode->GetRadians(),
|
||||||
|
swayed = swayAngle * base;
|
||||||
|
if (
|
||||||
|
base - swayed > GyroQuantiseEps ||
|
||||||
|
swayed - base > GyroQuantiseEps
|
||||||
|
)
|
||||||
|
{
|
||||||
|
eyeJointNode->SetRotation(Radian(swayed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Joint::BallJointType:
|
||||||
|
case Joint::BallTranslationJointType:
|
||||||
|
{
|
||||||
|
EulerAngles
|
||||||
|
base = eyeJointNode->GetEulerAngles();
|
||||||
|
EulerAngles
|
||||||
|
swayed(
|
||||||
|
Radian((Scalar)base.pitch * swayAngle),
|
||||||
|
Radian((Scalar)base.yaw * swayAngle),
|
||||||
|
Radian((Scalar)base.roll * swayAngle));
|
||||||
|
eyeJointNode->SetRotation(swayed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Gyroscope::WriteMechJoint()
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
if (
|
||||||
|
mechJointNode == NULL ||
|
||||||
|
mechJointNode->GetJointType() != Joint::BallTranslationJointType
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Point3D
|
||||||
|
current = mechJointNode->GetTranslation();
|
||||||
|
if (
|
||||||
|
current.x - eyePosition.x > GyroQuantiseEps ||
|
||||||
|
eyePosition.x - current.x > GyroQuantiseEps ||
|
||||||
|
current.y - eyePosition.y > GyroQuantiseEps ||
|
||||||
|
eyePosition.y - current.y > GyroQuantiseEps ||
|
||||||
|
current.z - eyePosition.z > GyroQuantiseEps ||
|
||||||
|
eyePosition.z - current.z > GyroQuantiseEps
|
||||||
|
)
|
||||||
|
{
|
||||||
|
mechJointNode->SetTranslation(
|
||||||
|
Point3D(eyePosition.x, eyePosition.y, eyePosition.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
EulerAngles
|
||||||
|
current = mechJointNode->GetEulerAngles();
|
||||||
|
if (
|
||||||
|
(Scalar)current.pitch - bodyOrientation.x > GyroQuantiseEps ||
|
||||||
|
bodyOrientation.x - (Scalar)current.pitch > GyroQuantiseEps ||
|
||||||
|
(Scalar)current.yaw - bodyOrientation.y > GyroQuantiseEps ||
|
||||||
|
bodyOrientation.y - (Scalar)current.yaw > GyroQuantiseEps ||
|
||||||
|
(Scalar)current.roll - bodyOrientation.z > GyroQuantiseEps ||
|
||||||
|
bodyOrientation.z - (Scalar)current.roll > GyroQuantiseEps
|
||||||
|
)
|
||||||
|
{
|
||||||
|
mechJointNode->SetRotation(
|
||||||
|
EulerAngles(
|
||||||
|
Radian(bodyOrientation.x),
|
||||||
|
Radian(bodyOrientation.y),
|
||||||
|
Radian(bodyOrientation.z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// The hit-bounce kicks (@004b2d8c / @004b2de4 / @004b2e50): negate the hit
|
||||||
|
// direction, scale by exageration, land in the force accumulators. The
|
||||||
|
// torque zeroes its .y then negates the WHOLE vector; the vertical kick
|
||||||
|
// keeps only the pitch component and mirrors it into .y.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Gyroscope::ApplyDamageImpulse(Scalar x, Scalar y, Scalar z, Scalar magnitude)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
Vector3D
|
||||||
|
direction(-x, -y, -z);
|
||||||
|
magnitude *= exageration;
|
||||||
|
direction *= magnitude;
|
||||||
|
eyeForce += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Gyroscope::ApplyDamageTorque(Scalar x, Scalar y, Scalar z, Scalar magnitude)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
Vector3D
|
||||||
|
direction(-x, -y, -z);
|
||||||
|
magnitude *= exageration;
|
||||||
|
direction *= magnitude;
|
||||||
|
bodyForce += direction;
|
||||||
|
bodyForce.y = 0.0f;
|
||||||
|
bodyForce.Negate(bodyForce);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Gyroscope::ApplyVerticalImpulse(Scalar pitch, Scalar magnitude)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
Vector3D
|
||||||
|
direction(pitch, 0.0f, 0.0f);
|
||||||
|
magnitude *= exageration;
|
||||||
|
direction *= magnitude;
|
||||||
|
bodyForce += direction;
|
||||||
|
bodyForce.y = bodyForce.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// @004b2980 -- the damage fan-out: every non-collision hit shakes the
|
||||||
|
// cockpit. Direction = the record's damageForce, or a random horizontal
|
||||||
|
// when it is ~zero; rotated into the yaw-only torso frame and re-normalized;
|
||||||
|
// per-type scaled amount / multiplier * response (Explosive alone reads
|
||||||
|
// burstCount); each channel clamped at 1.3 UPPER only; then the four kicks.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Gyroscope::ApplyDamageResponse(const Damage &damage)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
|
||||||
|
if (damage.damageAmount <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (damage.damageType == Damage::CollisionDamageType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
trans = 0.0f,
|
||||||
|
pitch_roll = 0.0f,
|
||||||
|
yaw = 0.0f,
|
||||||
|
vibration = 0.0f;
|
||||||
|
|
||||||
|
Vector3D
|
||||||
|
direction;
|
||||||
|
if (
|
||||||
|
damage.damageForce.x < 1.0e-4f && damage.damageForce.x > -1.0e-4f &&
|
||||||
|
damage.damageForce.y < 1.0e-4f && damage.damageForce.y > -1.0e-4f &&
|
||||||
|
damage.damageForce.z < 1.0e-4f && damage.damageForce.z > -1.0e-4f
|
||||||
|
)
|
||||||
|
{
|
||||||
|
direction.x = ((Scalar)Random >= 0.5f) ? (Scalar)Random : -(Scalar)Random;
|
||||||
|
direction.z = ((Scalar)Random >= 0.5f) ? (Scalar)Random : -(Scalar)Random;
|
||||||
|
direction.y = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
direction = damage.damageForce;
|
||||||
|
}
|
||||||
|
direction.Normalize(direction);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Rotate the WORLD hit direction into the yaw-only body frame (the
|
||||||
|
// torso twist), then re-normalize.
|
||||||
|
//
|
||||||
|
placeRot = Vector3D(0.0f, *externalPitchPtr, 0.0f);
|
||||||
|
placePos = Vector3D(0.0f, 0.0f, 0.0f);
|
||||||
|
{
|
||||||
|
AffineMatrix
|
||||||
|
frame;
|
||||||
|
frame = EulerAngles(
|
||||||
|
Radian(placeRot.x), Radian(placeRot.y), Radian(placeRot.z));
|
||||||
|
int
|
||||||
|
i;
|
||||||
|
for (i = 0; i < 12; ++i)
|
||||||
|
{
|
||||||
|
workMatrix[i] = frame.entries[i];
|
||||||
|
}
|
||||||
|
Vector3D
|
||||||
|
world = direction;
|
||||||
|
direction.x =
|
||||||
|
world.x * workMatrix[0] + world.y * workMatrix[1] +
|
||||||
|
world.z * workMatrix[2];
|
||||||
|
direction.y =
|
||||||
|
world.x * workMatrix[4] + world.y * workMatrix[5] +
|
||||||
|
world.z * workMatrix[6];
|
||||||
|
direction.z =
|
||||||
|
world.x * workMatrix[8] + world.y * workMatrix[9] +
|
||||||
|
world.z * workMatrix[10];
|
||||||
|
}
|
||||||
|
direction.Normalize(direction);
|
||||||
|
|
||||||
|
switch (damage.damageType)
|
||||||
|
{
|
||||||
|
case Damage::BallisticDamageType:
|
||||||
|
trans = damage.damageAmount / damageMultiplier[1] * damageResponse[1].trans;
|
||||||
|
pitch_roll = damage.damageAmount / damageMultiplier[1] * damageResponse[1].pitchRoll;
|
||||||
|
yaw = damage.damageAmount / damageMultiplier[1] * damageResponse[1].yaw;
|
||||||
|
vibration = damage.damageAmount / damageMultiplier[1] * damageResponse[1].vibration;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Damage::ExplosiveDamageType:
|
||||||
|
trans = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].trans;
|
||||||
|
pitch_roll = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].pitchRoll;
|
||||||
|
yaw = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].yaw;
|
||||||
|
vibration = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].vibration;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Damage::LaserDamageType:
|
||||||
|
trans = damage.damageAmount / damageMultiplier[3] * damageResponse[3].trans;
|
||||||
|
pitch_roll = damage.damageAmount / damageMultiplier[3] * damageResponse[3].pitchRoll;
|
||||||
|
yaw = damage.damageAmount / damageMultiplier[3] * damageResponse[3].yaw;
|
||||||
|
vibration = damage.damageAmount / damageMultiplier[3] * damageResponse[3].vibration;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Damage::EnergyDamageType:
|
||||||
|
trans = damage.damageAmount / damageMultiplier[4] * damageResponse[4].trans;
|
||||||
|
pitch_roll = damage.damageAmount / damageMultiplier[4] * damageResponse[4].pitchRoll;
|
||||||
|
yaw = damage.damageAmount / damageMultiplier[4] * damageResponse[4].yaw;
|
||||||
|
vibration = damage.damageAmount / damageMultiplier[4] * damageResponse[4].vibration;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trans > 1.3f) trans = 1.3f;
|
||||||
|
if (pitch_roll > 1.3f) pitch_roll = 1.3f;
|
||||||
|
if (yaw > 1.3f) yaw = 1.3f;
|
||||||
|
if (vibration > 1.3f) vibration = 1.3f;
|
||||||
|
|
||||||
|
ApplyDamageImpulse(direction.x, direction.y, direction.z, trans);
|
||||||
|
ApplyDamageTorque(direction.x, direction.y, direction.z, pitch_roll);
|
||||||
|
ApplyDamageImpulse(
|
||||||
|
vibrationDirection.x, vibrationDirection.y, vibrationDirection.z,
|
||||||
|
vibration);
|
||||||
|
ApplyVerticalImpulse(yaw, yaw);
|
||||||
|
}
|
||||||
|
|||||||
+201
-126
@@ -1,126 +1,201 @@
|
|||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
// File: gyro.hpp //
|
// File: gyro.hpp //
|
||||||
// Project: BattleTech Brick: Mech subsystems //
|
// Project: BattleTech Brick: Mech subsystems //
|
||||||
// Contents: Gyroscope -- the eye/body stabilisation subsystem //
|
// Contents: Gyroscope -- the eye/body stabilisation subsystem //
|
||||||
//---------------------------------------------------------------------------//
|
//---------------------------------------------------------------------------//
|
||||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||||
// All Rights reserved worldwide //
|
// All Rights reserved worldwide //
|
||||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
|
|
||||||
#if !defined(GYRO_HPP)
|
#if !defined(GYRO_HPP)
|
||||||
# define GYRO_HPP
|
# define GYRO_HPP
|
||||||
|
|
||||||
# if !defined(POWERSUB_HPP)
|
# if !defined(POWERSUB_HPP)
|
||||||
# include <powersub.hpp>
|
# include <powersub.hpp>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
//##################### Forward Class Declarations #######################
|
//##################### Forward Class Declarations #######################
|
||||||
class Mech;
|
class Mech;
|
||||||
|
|
||||||
//###########################################################################
|
//###########################################################################
|
||||||
//################### Gyroscope Model Resource #########################
|
//################### Gyroscope Model Resource #########################
|
||||||
//###########################################################################
|
//###########################################################################
|
||||||
|
|
||||||
struct Gyroscope__SubsystemResource:
|
//
|
||||||
public PowerWatcher::SubsystemResource
|
// One per-damage-type response row: how hard a hit of that type kicks
|
||||||
{
|
// the four gyro channels.
|
||||||
Scalar reservedF4;
|
//
|
||||||
Scalar exageration;
|
struct Gyroscope__DamageResponse
|
||||||
Scalar maxAnimationNoise;
|
{
|
||||||
Scalar minAnimationNoise;
|
Scalar trans;
|
||||||
Scalar rotationPerSecond;
|
Scalar pitchRoll;
|
||||||
Scalar percentageOnNormal;
|
Scalar yaw;
|
||||||
Scalar percentageOnDestruction;
|
Scalar vibration;
|
||||||
Scalar percentageOnDegradation;
|
};
|
||||||
Scalar percentageOnFailure;
|
|
||||||
Vector3D springConstant;
|
struct Gyroscope__SubsystemResource:
|
||||||
Vector3D dampingConstant;
|
public PowerWatcher::SubsystemResource
|
||||||
Vector3D posSpring;
|
{
|
||||||
Vector3D negSpring;
|
Scalar reservedF4;
|
||||||
Vector3D rotationSpringConstant;
|
Scalar exageration;
|
||||||
Vector3D rotationDampingConstant;
|
Scalar maxAnimationNoise;
|
||||||
Vector3D rotationPosSpring;
|
Scalar minAnimationNoise;
|
||||||
Vector3D rotationNegSpring;
|
Scalar rotationPerSecond;
|
||||||
char eyeJoint[32];
|
Scalar percentageOnNormal;
|
||||||
char mechJoint[32];
|
Scalar percentageOnDestruction;
|
||||||
Scalar collisionDamageMultiplier;
|
Scalar percentageOnDegradation;
|
||||||
Scalar ballisticDamageMultiplier;
|
Scalar percentageOnFailure;
|
||||||
Scalar explosiveDamageMultiplier;
|
Vector3D springConstant;
|
||||||
Scalar laserDamageMultiplier;
|
Vector3D dampingConstant;
|
||||||
Scalar energyDamageMultiplier;
|
Vector3D posSpring;
|
||||||
};
|
Vector3D negSpring;
|
||||||
|
Vector3D rotationSpringConstant;
|
||||||
//###########################################################################
|
Vector3D rotationDampingConstant;
|
||||||
//############################## Gyroscope #############################
|
Vector3D rotationPosSpring;
|
||||||
//###########################################################################
|
Vector3D rotationNegSpring;
|
||||||
//
|
char eyeJoint[32];
|
||||||
// The gyro drives the cockpit eye joint (view stabilisation) and the body
|
char mechJoint[32];
|
||||||
// lean. The tuning constants are read from the resource; the eye/body
|
Scalar collisionDamageMultiplier;
|
||||||
// dynamics state (springs, forces, velocities, work matrices) is advanced by
|
Scalar ballisticDamageMultiplier;
|
||||||
// the per-frame GyroscopeSimulation and held in dynamicsState until that
|
Scalar explosiveDamageMultiplier;
|
||||||
// (staged) method is reconstructed with the named fields.
|
Scalar laserDamageMultiplier;
|
||||||
//
|
Scalar energyDamageMultiplier;
|
||||||
class Gyroscope:
|
|
||||||
public PowerWatcher
|
//
|
||||||
{
|
// The five response quads follow the multipliers in the stream
|
||||||
public:
|
// (record size 0x21C -- binary-verified layout).
|
||||||
static Derivation ClassDerivations;
|
//
|
||||||
static SharedData DefaultData;
|
Gyroscope__DamageResponse collisionDamageResponse;
|
||||||
|
Gyroscope__DamageResponse ballisticDamageResponse;
|
||||||
typedef void
|
Gyroscope__DamageResponse explosiveDamageResponse;
|
||||||
(Gyroscope::*Performance)(Scalar time_slice);
|
Gyroscope__DamageResponse laserDamageResponse;
|
||||||
void
|
Gyroscope__DamageResponse energyDamageResponse;
|
||||||
SetPerformance(Performance performance)
|
};
|
||||||
{
|
|
||||||
Check(this);
|
//###########################################################################
|
||||||
activePerformance = (Simulation::Performance)performance;
|
//############################## Gyroscope #############################
|
||||||
}
|
//###########################################################################
|
||||||
|
//
|
||||||
static Logical
|
// The gyro drives the cockpit eye joint (view stabilisation) and the body
|
||||||
TestClass(Mech &);
|
// lean. The tuning constants are read from the resource; the eye/body
|
||||||
Logical
|
// dynamics state (springs, forces, velocities, work matrices) is advanced by
|
||||||
TestInstance() const;
|
// the per-frame GyroscopeSimulation and held in dynamicsState until that
|
||||||
void
|
// (staged) method is reconstructed with the named fields.
|
||||||
ResetToInitialState();
|
//
|
||||||
void
|
class Gyroscope:
|
||||||
GyroscopeSimulation(Scalar time_slice);
|
public PowerWatcher
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
typedef Gyroscope__SubsystemResource SubsystemResource;
|
static Derivation ClassDerivations;
|
||||||
|
static SharedData DefaultData;
|
||||||
Gyroscope(
|
|
||||||
Mech *owner,
|
typedef void
|
||||||
int subsystem_ID,
|
(Gyroscope::*Performance)(Scalar time_slice);
|
||||||
SubsystemResource *subsystem_resource,
|
void
|
||||||
SharedData &shared_data = DefaultData
|
SetPerformance(Performance performance)
|
||||||
);
|
{
|
||||||
~Gyroscope();
|
Check(this);
|
||||||
|
activePerformance = (Simulation::Performance)performance;
|
||||||
protected:
|
}
|
||||||
Scalar exageration;
|
|
||||||
Scalar maxAnimationNoise;
|
static Logical
|
||||||
Scalar minAnimationNoise;
|
TestClass(Mech &);
|
||||||
Scalar rotationPerSecond;
|
Logical
|
||||||
Scalar percentageOnNormal;
|
TestInstance() const;
|
||||||
Scalar percentageOnDestruction;
|
void
|
||||||
Scalar percentageOnDegradation;
|
ResetToInitialState();
|
||||||
Scalar percentageOnFailure;
|
void
|
||||||
Vector3D springConstant;
|
GyroscopeSimulation(Scalar time_slice);
|
||||||
Vector3D dampingConstant;
|
|
||||||
Vector3D posSpring;
|
//
|
||||||
Vector3D negSpring;
|
// The two integrators (called by the sim) and the two joint writes
|
||||||
Vector3D rotationSpringConstant;
|
// (called by the MECH master performance tail, after the gait --
|
||||||
Vector3D rotationDampingConstant;
|
// NOT by the sim; binary @0x4aaf74/83).
|
||||||
Vector3D rotationPosSpring;
|
//
|
||||||
Vector3D rotationNegSpring;
|
void
|
||||||
Scalar damageMultiplier[5];
|
IntegrateEyeJoint(Scalar time_slice);
|
||||||
//
|
void
|
||||||
// Eye/body dynamics accumulators advanced by GyroscopeSimulation
|
IntegrateBody(Scalar time_slice);
|
||||||
// (positions, velocities, forces, clamps, work matrices, sway).
|
void
|
||||||
// Reserved until the per-frame sim is reconstructed (phase 5).
|
WriteEyeJoint();
|
||||||
//
|
void
|
||||||
Scalar dynamicsState[64];
|
WriteMechJoint();
|
||||||
};
|
|
||||||
|
//
|
||||||
#endif
|
// The hit-bounce feeds. ApplyDamageResponse is the damage hub's
|
||||||
|
// fan-out (every non-collision hit shakes the cockpit); the three
|
||||||
|
// primitive kicks are also fed directly by the collision CRUNCH.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
ApplyDamageResponse(const Damage &damage);
|
||||||
|
void
|
||||||
|
ApplyDamageImpulse(Scalar x, Scalar y, Scalar z, Scalar magnitude);
|
||||||
|
void
|
||||||
|
ApplyDamageTorque(Scalar x, Scalar y, Scalar z, Scalar magnitude);
|
||||||
|
void
|
||||||
|
ApplyVerticalImpulse(Scalar pitch, Scalar magnitude);
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef Gyroscope__SubsystemResource SubsystemResource;
|
||||||
|
|
||||||
|
Gyroscope(
|
||||||
|
Mech *owner,
|
||||||
|
int subsystem_ID,
|
||||||
|
SubsystemResource *subsystem_resource,
|
||||||
|
SharedData &shared_data = DefaultData
|
||||||
|
);
|
||||||
|
~Gyroscope();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Scalar exageration;
|
||||||
|
Scalar maxAnimationNoise;
|
||||||
|
Scalar minAnimationNoise;
|
||||||
|
Scalar rotationPerSecond;
|
||||||
|
Scalar percentageOnNormal;
|
||||||
|
Scalar percentageOnDestruction;
|
||||||
|
Scalar percentageOnDegradation;
|
||||||
|
Scalar percentageOnFailure;
|
||||||
|
Vector3D springConstant;
|
||||||
|
Vector3D dampingConstant;
|
||||||
|
Vector3D posSpring;
|
||||||
|
Vector3D negSpring;
|
||||||
|
Vector3D rotationSpringConstant;
|
||||||
|
Vector3D rotationDampingConstant;
|
||||||
|
Vector3D rotationPosSpring;
|
||||||
|
Vector3D rotationNegSpring;
|
||||||
|
Scalar damageMultiplier[5];
|
||||||
|
Gyroscope__DamageResponse
|
||||||
|
damageResponse[5];
|
||||||
|
|
||||||
|
//
|
||||||
|
// The eye TRANSLATION spring state (the steady eye offset + hit
|
||||||
|
// bounce) and the body ROTATION spring state (the hull tip). The
|
||||||
|
// clamps are COPIES of the spring pairs, taken at ctor time (the
|
||||||
|
// body pair scaled by 2).
|
||||||
|
//
|
||||||
|
Vector3D eyePosition, eyeVelocity, eyeForce, eyeWork;
|
||||||
|
Vector3D eyeClampUpper, eyeClampLower;
|
||||||
|
Vector3D bodyOrientation, bodyVelocity, bodyForce, bodyWork;
|
||||||
|
Vector3D bodyClampUpper, bodyClampLower;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Sway (the animation-noise wander the sim slews between the
|
||||||
|
// powered/impaired percentages) and the placement scratch the
|
||||||
|
// damage fan-out rotates hit directions through.
|
||||||
|
//
|
||||||
|
Scalar swayAngle, swayBias, swayVelocity;
|
||||||
|
int swayActive;
|
||||||
|
Vector3D placeRot, placePos;
|
||||||
|
Scalar placeQuat[4];
|
||||||
|
Scalar workMatrix[12];
|
||||||
|
Scalar spare0;
|
||||||
|
Scalar *externalPitchPtr; // -> the torso twist (yaw-only hit
|
||||||
|
// frame); self-points at spare0
|
||||||
|
// until a torso binds it
|
||||||
|
Vector3D vibrationDirection; // the fixed up axis
|
||||||
|
Joint *eyeJointNode;
|
||||||
|
Joint *mechJointNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,67 +1,70 @@
|
|||||||
# GYRO.CPP — reconstruction notes
|
# GYRO.CPP — reconstruction notes
|
||||||
|
|
||||||
**Status: ctor + streaming RECONSTRUCTED; the per-frame simulation is the
|
**Status: FULLY RECONSTRUCTED AND WIRED (2026-08-03). The simulation,
|
||||||
in-flight increment (plan below). Donor: BT411 gyro.cpp (1106 lines, task
|
both integrators, both joint writes, all four damage hooks, and the three
|
||||||
#56 BYTE-EXACT [T1] on the integrators and the damage fan-out).**
|
feeds (damage hub, collision crunch, master-perf joint-write dispatch) are
|
||||||
|
live; the registered Performance ticks without fault. Deep verification --
|
||||||
## Why this TU is the feel wave's cornerstone
|
the visible cockpit bounce under fire -- is an operator/rig item. Donor:
|
||||||
|
BT411 gyro.cpp (task #56 byte-exact).**
|
||||||
The gyro owns the cockpit's physical feel: the eye TRANSLATION spring (the
|
|
||||||
steady eye offset + hit bounce, written to the 'jointeye' BallTranslation
|
## Why this TU is the feel wave's cornerstone
|
||||||
joint that `siteeyepoint` rides) and the body ROTATION spring (the hull tip).
|
|
||||||
The 5.3.99 collision block's staged "gyro crunch" feed and the damage hub's
|
The gyro owns the cockpit's physical feel: the eye TRANSLATION spring (the
|
||||||
staged hit-bounce both land here.
|
steady eye offset + hit bounce, written to the 'jointeye' BallTranslation
|
||||||
|
joint that `siteeyepoint` rides) and the body ROTATION spring (the hull tip).
|
||||||
## The transcription plan (all donor line refs gathered 2026-08-03)
|
The 5.3.99 collision block's staged "gyro crunch" feed and the damage hub's
|
||||||
|
staged hit-bounce both land here.
|
||||||
1. **Members** (carve `dynamicsState[64]`): eyePosition/eyeVelocity/eyeForce/
|
|
||||||
eyeWork (Vector3D), bodyOrientation/bodyVelocity/bodyForce/bodyWork,
|
## The transcription plan (all donor line refs gathered 2026-08-03)
|
||||||
swayAngle/swayBias/swayVelocity/swayActive, placeRot/placePos,
|
|
||||||
workMatrix[12], externalPitchPtr (the torso-twist Scalar*), eyeJointNode/
|
1. **Members** (carve `dynamicsState[64]`): eyePosition/eyeVelocity/eyeForce/
|
||||||
mechJointNode (Joint*), damageResponse[5] {trans,pitchRoll,yaw,vibration},
|
eyeWork (Vector3D), bodyOrientation/bodyVelocity/bodyForce/bodyWork,
|
||||||
vibrationDirection (fixed up axis). Clamps are ALIASES: eyeClampUpper ==
|
swayAngle/swayBias/swayVelocity/swayActive, placeRot/placePos,
|
||||||
posSpring, eyeClampLower == negSpring; body likewise on the rotation pair.
|
workMatrix[12], externalPitchPtr (the torso-twist Scalar*), eyeJointNode/
|
||||||
2. **GyroscopeSimulation** (@004b275c): PowerWatcher::Simulation(dt); the
|
mechJointNode (Joint*), damageResponse[5] {trans,pitchRoll,yaw,vibration},
|
||||||
impaired pick (HeatModelOff || electrical != Ready || heat == FailureHeat)
|
vibrationDirection (fixed up axis). Clamps are ALIASES: eyeClampUpper ==
|
||||||
selects percentageOnDestruction vs Normal + swayBias as the sway target;
|
posSpring, eyeClampLower == negSpring; body likewise on the rotation pair.
|
||||||
slew by rotationPerSecond*dt with no-overshoot clamps, band-clamp into
|
2. **GyroscopeSimulation** (@004b275c): PowerWatcher::Simulation(dt); the
|
||||||
[minAnimationNoise, maxAnimationNoise]; IntegrateEyeJoint; IntegrateBody.
|
impaired pick (HeatModelOff || electrical != Ready || heat == FailureHeat)
|
||||||
The performance ENDS there — WriteEyeJoint/WriteMechJoint are called from
|
selects percentageOnDestruction vs Normal + swayBias as the sway target;
|
||||||
the MECH master perf tail (@0x4aaf74/83), i.e. our Simulate gait seam,
|
slew by rotationPerSecond*dt with no-overshoot clamps, band-clamp into
|
||||||
with the death/leg-anim gates.
|
[minAnimationNoise, maxAnimationNoise]; IntegrateEyeJoint; IntegrateBody.
|
||||||
3. **IntegrateEyeJoint** (@004b2ec0, byte-exact): both spring terms use
|
The performance ENDS there — WriteEyeJoint/WriteMechJoint are called from
|
||||||
springConstant; damping OVERWRITES the force accumulator (carries into
|
the MECH master perf tail (@0x4aaf74/83), i.e. our Simulate gait seam,
|
||||||
next frame by design); position += velocity with NO dt; per-axis clamp
|
with the death/leg-anim gates.
|
||||||
min-vs-posSpring then max-vs-negSpring.
|
3. **IntegrateEyeJoint** (@004b2ec0, byte-exact): both spring terms use
|
||||||
4. **IntegrateBody** (@004b30ec): same shape, three quirks — spring force
|
springConstant; damping OVERWRITES the force accumulator (carries into
|
||||||
X/Z-CROSSED (k.x*d.z, k.y*d.y, k.z*d.x); damping componentwise uncrossed;
|
next frame by design); position += velocity with NO dt; per-axis clamp
|
||||||
orientation += velocity CROSSED again (x+=v.z, z+=v.x). Clamp vs the
|
min-vs-posSpring then max-vs-negSpring.
|
||||||
rotation spring pair.
|
4. **IntegrateBody** (@004b30ec): same shape, three quirks — spring force
|
||||||
5. **WriteEyeJoint** (@004b33e0): swayAngle onto the EyeJoint node — scalar
|
X/Z-CROSSED (k.x*d.z, k.y*d.y, k.z*d.x); damping componentwise uncrossed;
|
||||||
channel scaled for hinge types <3, vector channel scaled for 4/5, write
|
orientation += velocity CROSSED again (x+=v.z, z+=v.x). Clamp vs the
|
||||||
only past QuantiseEps. **WriteMechJoint** (@004b34ec): 'jointeye' type-5
|
rotation spring pair.
|
||||||
ONLY: SetTranslation(eyePosition) + SetRotation(EulerAngles from
|
5. **WriteEyeJoint** (@004b33e0): swayAngle onto the EyeJoint node — scalar
|
||||||
bodyOrientation), each QuantiseEps-gated.
|
channel scaled for hinge types <3, vector channel scaled for 4/5, write
|
||||||
6. **Damage hooks**: ApplyDamageImpulse (negate dir, *exageration, +=
|
only past QuantiseEps. **WriteMechJoint** (@004b34ec): 'jointeye' type-5
|
||||||
eyeForce); ApplyDamageTorque (same into bodyForce, then bodyForce.y = 0,
|
ONLY: SetTranslation(eyePosition) + SetRotation(EulerAngles from
|
||||||
then whole-vector NEGATE); ApplyVerticalImpulse (x-only kick, .y mirrors
|
bodyOrientation), each QuantiseEps-gated.
|
||||||
.x). **ApplyDamageResponse** (@004b2980, byte-exact): no-op on Collision
|
6. **Damage hooks**: ApplyDamageImpulse (negate dir, *exageration, +=
|
||||||
or zero amount; direction = damageForce or random-horizontal when ~0;
|
eyeForce); ApplyDamageTorque (same into bodyForce, then bodyForce.y = 0,
|
||||||
rotate into the yaw-only torso frame (externalPitchPtr) and re-normalize;
|
then whole-vector NEGATE); ApplyVerticalImpulse (x-only kick, .y mirrors
|
||||||
per-type scale amount/multiplier[type]*response[type].{4 terms}
|
.x). **ApplyDamageResponse** (@004b2980, byte-exact): no-op on Collision
|
||||||
(Explosive alone multiplies burstCount); clamp each at 1.3 UPPER only;
|
or zero amount; direction = damageForce or random-horizontal when ~0;
|
||||||
then the four kicks: impulse(dir,trans), torque(dir,pitchRoll),
|
rotate into the yaw-only torso frame (externalPitchPtr) and re-normalize;
|
||||||
vibration along vibrationDirection, vertical(yaw term).
|
per-type scale amount/multiplier[type]*response[type].{4 terms}
|
||||||
7. **Wiring**: the damage hub's staged gyro feed (MECH.CPP
|
(Explosive alone multiplies burstCount); clamp each at 1.3 UPPER only;
|
||||||
TakeDamageMessageHandler step 1) calls ApplyDamageResponse; the crushable
|
then the four kicks: impulse(dir,trans), torque(dir,pitchRoll),
|
||||||
CRUNCH branch calls Torque(n,0.4)+Impulse(up,0.2) (already-shaped site);
|
vibration along vibrationDirection, vertical(yaw term).
|
||||||
Simulate's gait seam calls WriteEyeJoint+WriteMechJoint on the master
|
7. **Wiring**: the damage hub's staged gyro feed (MECH.CPP
|
||||||
after the gait advances (death/leg gates per the binary tail).
|
TakeDamageMessageHandler step 1) calls ApplyDamageResponse; the crushable
|
||||||
8. **Family bridge**: Mech::DistributeCollisionDamage already references
|
CRUNCH branch calls Torque(n,0.4)+Impulse(up,0.2) (already-shaped site);
|
||||||
Gyroscope::ClassDerivations directly — no ODR bridge needed in our tree.
|
Simulate's gait seam calls WriteEyeJoint+WriteMechJoint on the master
|
||||||
|
after the gait advances (death/leg gates per the binary tail).
|
||||||
## Verification plan
|
8. **Family bridge**: Mech::DistributeCollisionDamage already references
|
||||||
|
Gyroscope::ClassDerivations directly — no ODR bridge needed in our tree.
|
||||||
BT_GYRO_LOG prints the joint writes + NaN sentinel; a live arena run with
|
|
||||||
enemy fire should show [gyro-dmg] fan-outs and 'jointeye' translation motion
|
## Verification plan
|
||||||
on the wire (the cockpit eye rides it — visible as view bounce on hits).
|
|
||||||
|
BT_GYRO_LOG prints the joint writes + NaN sentinel; a live arena run with
|
||||||
|
enemy fire should show [gyro-dmg] fan-outs and 'jointeye' translation motion
|
||||||
|
on the wire (the cockpit eye rides it — visible as view bounce on hits).
|
||||||
|
|||||||
@@ -1078,12 +1078,13 @@ void
|
|||||||
Check(this);
|
Check(this);
|
||||||
Check(message);
|
Check(message);
|
||||||
|
|
||||||
if (gyroSubsystem != NULL && getenv("BT_MECH_LOG"))
|
//
|
||||||
|
// The RAW record feeds the gyro FIRST -- even an invalid-zone hit shakes
|
||||||
|
// the cockpit (binary hub order step 1).
|
||||||
|
//
|
||||||
|
if (gyroSubsystem != NULL)
|
||||||
{
|
{
|
||||||
DEBUG_STREAM << "[gyro] hit feed staged (type="
|
((Gyroscope *)gyroSubsystem)->ApplyDamageResponse(message->damageData);
|
||||||
<< (int)message->damageData.damageType
|
|
||||||
<< " amt=" << message->damageData.damageAmount << ")"
|
|
||||||
<< endl << flush;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastInflictingID = message->inflictingEntity;
|
lastInflictingID = message->inflictingEntity;
|
||||||
@@ -1613,6 +1614,19 @@ void
|
|||||||
AdvanceLegAnimation(time_slice);
|
AdvanceLegAnimation(time_slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The gyro's joint writes (binary master-perf tail @0x4aaf74/83, AFTER
|
||||||
|
// the animation pass): the idle sway onto the EyeJoint, and the
|
||||||
|
// integrated eye offset + body tip onto 'jointeye' -- the joint the
|
||||||
|
// cockpit eyepoint rides. Gated off during the death clips exactly as
|
||||||
|
// the tail is.
|
||||||
|
//
|
||||||
|
if (gyroSubsystem != NULL && MovementMode() < 5)
|
||||||
|
{
|
||||||
|
((Gyroscope *)gyroSubsystem)->WriteEyeJoint();
|
||||||
|
((Gyroscope *)gyroSubsystem)->WriteMechJoint();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// THE TURN-IN-PLACE DISPATCHER (master perf @0x4aa505-0x4aa588, decoded
|
// THE TURN-IN-PLACE DISPATCHER (master perf @0x4aa505-0x4aa588, decoded
|
||||||
// from raw disasm -- Ghidra never decompiled the function). From
|
// from raw disasm -- Ghidra never decompiled the function). From
|
||||||
@@ -2009,6 +2023,23 @@ void
|
|||||||
localOrigin.linearPosition = post_snap_position;
|
localOrigin.linearPosition = post_snap_position;
|
||||||
localToWorld = localOrigin;
|
localToWorld = localOrigin;
|
||||||
MoveCollisionVolume();
|
MoveCollisionVolume();
|
||||||
|
|
||||||
|
//
|
||||||
|
// The gyro CRUNCH (binary @4aa7ce-4aa871): torque 0.4 along the
|
||||||
|
// normalized contact force, upward impulse 0.2. The Normalize
|
||||||
|
// is unguarded in the binary too.
|
||||||
|
//
|
||||||
|
if (gyroSubsystem != NULL)
|
||||||
|
{
|
||||||
|
Vector3D
|
||||||
|
crunch_normal;
|
||||||
|
crunch_normal.Normalize(collision_damage.damageForce);
|
||||||
|
((Gyroscope *)gyroSubsystem)->ApplyDamageTorque(
|
||||||
|
crunch_normal.x, crunch_normal.y, crunch_normal.z, 0.4f);
|
||||||
|
((Gyroscope *)gyroSubsystem)->ApplyDamageImpulse(
|
||||||
|
0.0f, 1.0f, 0.0f, 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
collision_damage.damageAmount = 0.0f;
|
collision_damage.damageAmount = 0.0f;
|
||||||
|
|
||||||
if (getenv("BT_MECH_LOG"))
|
if (getenv("BT_MECH_LOG"))
|
||||||
|
|||||||
Reference in New Issue
Block a user