//===========================================================================// // File: mech.cpp // // Project: BattleTech Brick: Entity Manager // // Contents: Implementation details for the Mech entity // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(MECH_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(MEMSTRM_HPP) # include #endif #if !defined(RESOURCE_HPP) # include #endif // The subsystem roster the segment walk instantiates. #include // HeatableSubsystem, HeatSink, HeatWatcher, Condenser #include // PoweredSubsystem, PowerWatcher, Generator #include // Reservoir #include // Sensor #include // Gyroscope #include // Torso #include // Myomers #include // HUD #include // Searchlight #include // ThermalSight #include // MechTech #include // SubsystemMessageManager #include // MechWeapon #include // Emitter #include // PPC #include // GaussRifle #include // ProjectileWeapon #include // MissileLauncher #include // AmmoBin #include // MechControlsMapper -- the drive reads its demands #include // Joint / JointSubsystem -- ResolveJoint #include // EntitySegment -- the skeleton segment table // //############################################################################# //############################################################################# // Derivation Mech::ClassDerivations( JointedMover::ClassDerivations, "Mech" ); Mech::SharedData Mech::DefaultData( Mech::ClassDerivations, JointedMover::MessageHandlers, JointedMover::AttributeIndex, 33, (Entity::MakeHandler)Mech::Make ); // //############################################################################# //############################################################################# // Mech* Mech::Make(MakeMessage *creation_message) { return new Mech(creation_message); } // //############################################################################# // Mech ctor -- the heart of the entity (walks the model segment table and // instantiates the full subsystem roster: power, heat, weapons, actuators, // controls, tech, damage zones). This is the largest single function in the // game and the current reconstruction frontier; see MECH.NOTES.md. Chains to // the JointedMover base so the skeleton/segments stream before it Fails. //############################################################################# // Mech::Mech( MakeMessage *creation_message, SharedData &shared_data ): JointedMover(creation_message, shared_data), controllableSubsystems(this), watchedSubsystems(this), heatableSubsystems(this), weaponRoster(this), damageableSubsystems(this) { Check_Pointer(creation_message); // // Cached subsystem back-pointers -- filled by the segment walk below. // sensorSubsystem = NULL; gyroSubsystem = NULL; sinkSourceSubsystem = NULL; hudSubsystem = NULL; messageManager = NULL; weaponCount = 0; // // Embedded status / animation / naming state. // mechNameFilter.Initialize(); masterAlarm.Initialize(0x21); heatAlarm.Initialize(3); stabilityAlarm.Initialize(2); statusAlarm.Initialize(0x21); targetReticle.reticleState = Reticle::ReticleOn; targetReticle.pickPointingOn = True; targetReticle.reticleElementMask = Reticle::AllEnabledGroup; animationState = StateIndicator(0x21); animationState.SetState(0); replicantAnimationState = StateIndicator(0x21); replicantAnimationState.SetState(0); collisionState = StateIndicator(4); collisionState.SetState(0); { for (int i = 0; i < 5; ++i) { telemetryFilter[i].SetSize(15, 0.0f); } } legAnimation.Init(this); bodyAnimation.Init(this); // // Locomotion parameters. BRING-UP DEFAULTS: the authentic values come from // the Mech model resource (WalkingTurnRate / RunningTurnRate / MaxAcceleration) // and LoadLocomotionClips (the stride/top speeds measured from the walk/run // animation clips). Wiring those in is a later refinement (needs the model- // resource pointer + clip loader); until then these sane defaults make the // mech drivable with the authentic control-interpretation + drive math. // walkingTurnRate = 50.0f * RAD_PER_DEG; // rad/s (walk / turn-in-place) runningTurnRate = 25.0f * RAD_PER_DEG; // rad/s (at run speed) reverseStrideLength = 30.0f; // top/run speed (u/s) walkStrideLength = 12.0f; // walk speed (u/s) reverseSpeedMax = 2.0f; // low-speed turn-rate gate (u/s) forwardThrottleScale= 1.0f; maxBodyAcceleration = 30.0f; // u/s^2 bodyTargetSpeed = 0.0f; currentBodySpeed = 0.0f; eyepointRotation = EulerAngles::Identity; lookPitch = 0.0f; lookYaw = 0.0f; // // Look-view angles: defaults until the GameModel read below overrides them // with the authored per-mech values. // lookLeftAngle = 90.0f * RAD_PER_DEG; lookRightAngle = -90.0f * RAD_PER_DEG; lookFrontAngle = -30.0f * RAD_PER_DEG; lookBackAngle = 0.0f; { for (int i = 0; i < 202; ++i) { reservedState[i] = 0; } } // //----------------------------------------------------------------------- // Segment-table walk: instantiate one Subsystem per streamed segment, // dispatching on its classID. The subsystem roster (subsystemArray / // subsystemCount) lives in the base Entity. //----------------------------------------------------------------------- // ResourceDescription::ResourceID modelResourceID = creation_message->resourceID; ResourceDescription *subsystemDesc = application->GetResourceFile()->SearchList( modelResourceID, ResourceDescription::SubsystemModelStreamResourceType ); Check(subsystemDesc); subsystemDesc->Lock(); // // Copy the raw stream into a padded buffer: reading a SubsystemResource // struct off the tail segment can over-read the raw resource, so pad it. // size_t rawSize = (size_t)subsystemDesc->resourceSize; size_t padSize = rawSize + 0x400; void *padBuffer = (void *)new char[padSize]; memcpy(padBuffer, subsystemDesc->resourceAddress, rawSize); MemoryStream subsystemStream(padBuffer, padSize); int streamedSubsystemCount = *(int *)subsystemStream.GetPointer(); subsystemStream.AdvancePointer(sizeof(int)); // // Slot 0 = the (later-installed) control mapper, slot 1 = the voltage bus // sentinel; the streamed subsystems fill from slot 2. // subsystemCount = streamedSubsystemCount + 2; subsystemArray = new Subsystem *[subsystemCount]; { for (int z = 0; z < subsystemCount; ++z) { subsystemArray[z] = NULL; } } for (int id = 2; id < subsystemCount; ++id) { Subsystem::SubsystemResource *seg = (Subsystem::SubsystemResource *)subsystemStream.GetPointer(); Subsystem *made = NULL; switch (seg->classID) { case CondenserClassID: made = new Condenser(this, id, (Condenser::SubsystemResource *)seg); break; case HeatSinkClassID: made = new HeatSink(this, id, (HeatSink::SubsystemResource *)seg); break; case HeatWatcherClassID: made = new HeatWatcher(this, id, (HeatWatcher::SubsystemResource *)seg); break; case ReservoirClassID: made = new Reservoir(this, id, (Reservoir::SubsystemResource *)seg); break; case GeneratorClassID: made = new Generator(this, id, (Generator::SubsystemResource *)seg); break; case PoweredSubsystemClassID: made = new PoweredSubsystem(this, id, (PoweredSubsystem::SubsystemResource *)seg); break; case SensorClassID: made = new Sensor(this, id, (Sensor::SubsystemResource *)seg); sensorSubsystem = made; break; case GyroscopeClassID: made = new Gyroscope(this, id, (Gyroscope::SubsystemResource *)seg); gyroSubsystem = made; break; case TorsoClassID: made = new Torso(this, id, (Torso::SubsystemResource *)seg); sinkSourceSubsystem = made; break; case MyomersClassID: made = new Myomers(this, id, (Myomers::SubsystemResource *)seg); break; case EmitterClassID: made = new Emitter(this, id, (Emitter::SubsystemResource *)seg); ++weaponCount; break; case PPCClassID: made = new PPC(this, id, (PPC::SubsystemResource *)seg, PPC::DefaultData); ++weaponCount; break; case AmmoBinClassID: made = new AmmoBin(this, id, (AmmoBin::SubsystemResource *)seg); break; case ProjectileWeaponClassID: made = new ProjectileWeapon(this, id, (ProjectileWeapon::SubsystemResource *)seg); ++weaponCount; break; case GaussRifleClassID: made = new GaussRifle(this, id, (GaussRifle::SubsystemResource *)seg); ++weaponCount; break; case MissileLauncherClassID: made = new MissileLauncher(this, id, (MissileLauncher::SubsystemResource *)seg); ++weaponCount; break; case SubsystemMessageManagerClassID: made = new SubsystemMessageManager(this, id, (SubsystemMessageManager::SubsystemResource *)seg); messageManager = (SubsystemMessageManager *)made; break; case HUDClassID: made = new HUD(this, id, (HUD::SubsystemResource *)seg); hudSubsystem = made; break; case SearchlightClassID: made = new Searchlight(this, id, (Searchlight::SubsystemResource *)seg); break; case ThermalSightClassID: made = new ThermalSight(this, id, (ThermalSight::SubsystemResource *)seg); break; case MechTechClassID: made = new MechTech(this, id, (MechTech::SubsystemResource *)seg); break; case EmitterClassID + 1: // LaserClassID -- an Emitter energy weapon case EmitterClassID + 2: // ParticleCannonClassID -- an Emitter energy weapon made = new Emitter(this, id, (Emitter::SubsystemResource *)seg); ++weaponCount; break; default: // // Unrecognised / not-yet-reconstructed subsystem class (Capacitor, // AmmoFeeder, Radar, Turret, ...): give the slot a base // MechSubsystem so control/damage bindings that resolve this // subsystemID find a real (if generic) subsystem rather than a NULL // plug. The roster stays aligned. // made = new MechSubsystem( this, id, (MechSubsystem::SubsystemResource *)seg, MechSubsystem::DefaultData ); break; } subsystemArray[id] = made; subsystemStream.AdvancePointer(seg->subsystemModelSize); } subsystemDesc->Unlock(); delete [] (char *)padBuffer; if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[mech] segment walk done: subsystemCount=" << subsystemCount << " weaponCount=" << weaponCount << endl << flush; // // Skeleton summary: confirm the JointedMover base streamed the segment / // joint tables (so joint-driven aim / animation / damage has something to // bind to). BT_SKEL_DUMP additionally lists every segment name + joint // index (used to identify the twist / gun / leg joints). // JointSubsystem *joints = GetJointSubsystem(); DEBUG_STREAM << "[skel] jointSubsystem=" << (void *)joints << " jointCount=" << (joints ? joints->GetJointCount() : -1) << endl << flush; if (getenv("BT_SKEL_DUMP")) { EntitySegment::SegmentTableIterator it(segmentTable); EntitySegment *seg; int i = 0; while ((seg = it.ReadAndNext()) != NULL && i < 60) { DEBUG_STREAM << "[skel] seg[" << i << "] name=" << seg->GetName() << " jointIdx=" << seg->GetJointIndex() << endl; ++i; } DEBUG_STREAM << "[skel] segments=" << i << endl << flush; // // The subsystem roster map (slot -> name), for cross-referencing the // streamed index fields (voltage source / linked sink / ammo bin). // for (int r = 2; r < subsystemCount; ++r) { if (subsystemArray[r] != NULL) { DEBUG_STREAM << "[roster] slot " << r << " = " << subsystemArray[r]->GetName() << endl; } } DEBUG_STREAM << flush; } } // //----------------------------------------------------------------------- // Source the authentic per-mech locomotion params from the GameModel // resource (mech.cpp @~1430: walkingTurnRate/runningTurnRate deg->rad, // maxAcceleration, throttleAdjustment). The reconstructed ModelResource // struct layout is only partially verified (BT411 flags it mis-decoded in // places), so every read is SANITY-GUARDED: a value outside a sane band // leaves the bring-up default in place. The stride/top speeds still come // from the bring-up defaults (their authentic source is LoadLocomotionClips, // which measures them from the walk/run animation clips -- a later wave). //----------------------------------------------------------------------- // { ResourceDescription *modelDesc = application->GetResourceFile()->SearchList( modelResourceID, ResourceDescription::GameModelResourceType ); if (modelDesc != NULL) { modelDesc->Lock(); ModelResource *model = (ModelResource *)modelDesc->resourceAddress; if (model != NULL) { if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[mech] model params: walkTR=" << model->walkingTurnRate << " runTR=" << model->runningTurnRate << " maxAcc=" << model->maxAcceleration << " throttleAdj=" << model->throttleAdjustment << " (deg,deg,u/s^2,scale)" << endl << flush; DEBUG_STREAM << "[mech] look angles: L=" << model->lookLeftAngle << " R=" << model->lookRightAngle << " F=" << model->lookFrontAngle << " B=" << model->lookBackAngle << " (deg)" << endl << flush; } if (model->walkingTurnRate > 1.0f && model->walkingTurnRate < 360.0f) { walkingTurnRate = model->walkingTurnRate * RAD_PER_DEG; } if (model->runningTurnRate > 1.0f && model->runningTurnRate < 360.0f) { runningTurnRate = model->runningTurnRate * RAD_PER_DEG; } if (model->maxAcceleration > 1.0f && model->maxAcceleration < 500.0f) { maxBodyAcceleration = model->maxAcceleration; } if (model->throttleAdjustment > 0.05f && model->throttleAdjustment < 20.0f) { forwardThrottleScale = model->throttleAdjustment; } // // The authored look-view angles (deg->rad), same insanity band // as the rest of the guarded reads. // { Scalar a; a = model->lookLeftAngle; if (a > -360.0f && a < 360.0f) lookLeftAngle = a * RAD_PER_DEG; a = model->lookRightAngle; if (a > -360.0f && a < 360.0f) lookRightAngle = a * RAD_PER_DEG; a = model->lookFrontAngle; if (a > -360.0f && a < 360.0f) lookFrontAngle = a * RAD_PER_DEG; a = model->lookBackAngle; if (a > -360.0f && a < 360.0f) lookBackAngle = a * RAD_PER_DEG; } } modelDesc->Unlock(); } } // // Install the per-frame body Performance. Until now the mech ran the base // DoNothingOnce; from here the engine dispatches Mech::Simulate every frame // (Mover -> Entity -> Simulation::PerformAndWatch) once the mission is // RunningMission. // SetPerformance(&Mech::Simulate); // // The entity is complete -- mark it VALID, like every 1995 entity ctor tail // (CamShip / DoorFrame / DropZone / ...). LOAD-BEARING: Entity::Dispatch // routes messages to an INVALID entity into the deferred event queue, so // without this the mech never receives a directly-dispatched message -- // the PlayerLink bind (and with it every player-experience gate) silently // never lands. // SetValidFlag(); Check_Fpu(); } Mech::~Mech() { } void Mech::SetMappingSubsystem(Subsystem *subsystem) { Check(this); Check_Pointer(subsystemArray); // // The control mapper lives in roster slot 0 (the streamed control-mapping // resource binds its DirectMappings to subsystemID 0, so it must resolve // there via Entity::GetSimulation(0)). On a re-spawn, drop the old one. // if (subsystemArray[0] != NULL) { Unregister_Object(subsystemArray[0]); delete subsystemArray[0]; } subsystemArray[0] = subsystem; } // //############################################################################# // ResolveJoint -- the shared skeleton-joint resolver (mech.cpp @00424b60). // A subsystem hands us the joint NAME from its resource; we look up the // skeleton segment of that name, read its joint index, and fetch the animated // Joint from the JointSubsystem. NULL for an empty/unknown name or a mech with // no skeleton/joint subsystem. //############################################################################# // Joint* Mech::ResolveJoint(const char *joint_name) { Check(this); if (joint_name == NULL || joint_name[0] == '\0') { return NULL; } EntitySegment *segment = GetSegment(CString(joint_name)); if (segment == NULL) { return NULL; } JointSubsystem *joints = GetJointSubsystem(); if (joints == NULL) { return NULL; } return joints->GetJoint(segment->GetJointIndex()); } // //############################################################################# // CommitLookState -- the look-button eyepoint commit (the binary's five-state // look machine tail, controls mapper part_013.c:396-459). Re-aims the eyepoint // from the model's authored look angles: side looks yaw by lookLeft/RightAngle, // look-behind is yaw pi with lookBackAngle pitch, look-down pitches by // lookFrontAngle, forward is identity. The committed pitch/yaw are stored in // lookPitch/lookYaw so the per-frame compose in Simulate can keep adding the // live Torso elevation on top. // // Also part of the authentic commit, deferred to the weapon wave: re-arming // each weapon's view-fire enable (forward view = the non-rear-mounted weapons, // look-back = the rear-mounted ones, side/down = none) and flipping the HUD pip // group mask (forward = front group, look-back = rear group) -- both need the // MechWeapon viewFireEnable/rearFiring members, not yet reconstructed. //############################################################################# // void Mech::CommitLookState(int look_state) { Check(this); Scalar pitch = 0.0f; Scalar yaw = 0.0f; switch (look_state) { case MechControlsMapper::LookLeftState: yaw = lookLeftAngle; break; case MechControlsMapper::LookRightState: yaw = lookRightAngle; break; case MechControlsMapper::LookBehindState: yaw = PI; pitch = lookBackAngle; break; case MechControlsMapper::LookDownState: pitch = lookFrontAngle; break; default: break; // LookNone: identity } lookPitch = pitch; lookYaw = yaw; eyepointRotation = EulerAngles( Radian(Radian::Normalize(pitch)), Radian(Radian::Normalize(yaw)), Radian(0.0f) ); // // Re-arm each weapon's view-fire enable: the forward view arms the // non-rear-mounted weapons, LOOK-BACK arms the rear-mounted ones, and the // side/down views arm none. // { for (int id = 2; id < subsystemCount; ++id) { Subsystem *sub = subsystemArray[id]; if (sub == NULL || !sub->IsDerivedFrom(MechWeapon::ClassDerivations)) { continue; } MechWeapon *weapon = (MechWeapon *)sub; Logical arm; if (look_state == MechControlsMapper::LookNone) { arm = (weapon->IsRearFiring() == False); } else if (look_state == MechControlsMapper::LookBehindState) { arm = weapon->IsRearFiring(); } else { arm = False; } weapon->SetViewFireEnable(arm); if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[look] weapon '" << weapon->GetName() << "' rear=" << (int)weapon->IsRearFiring() << " armed=" << (int)arm << endl << flush; } } } // // The HUD reticle weapon-pip group: the forward view shows the FRONT pip // group, look-back shows the REAR group; side/down views leave the mask. // if (look_state == MechControlsMapper::LookNone) { targetReticle.reticleElementMask = (Reticle::ReticleElements) (((int)targetReticle.reticleElementMask | Reticle::FrontFiringWeaponsOn) & ~Reticle::RearFiringWeaponsOn); } else if (look_state == MechControlsMapper::LookBehindState) { targetReticle.reticleElementMask = (Reticle::ReticleElements) (((int)targetReticle.reticleElementMask | Reticle::RearFiringWeaponsOn) & ~Reticle::FrontFiringWeaponsOn); } if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[look] state=" << look_state << " yaw=" << yaw << " pitch=" << pitch << " pipMask=0x" << hex << (int)targetReticle.reticleElementMask << dec << endl << flush; } Check_Fpu(); } // //############################################################################# // Simulate -- the mech's per-frame body Performance (the Mover locomotion tick). // // FUNCTIONAL MOTION CORE (Phase 5.3, increment 1). A Mech is a Mover; its // per-frame job is to advance its Origin and commit it to the world transform. // This reconstructs the load-bearing spine of the 1995 Mech::Simulate // (mech4.cpp @004ab430): integrate the body velocity into localOrigin, then // rebuild localToWorld with the engine's own idiom (ENTITY.CPP:988 / // MOVER.CPP:850, `localToWorld = localOrigin`). // // Still layered on top of this (next increments): // * the gait-cycle self-propulsion that FEEDS worldLinearVelocity -- the // authentic model drives forward speed from the walk/run animation cycle // (IntegrateMotion -> AdvanceBodyAnimation -> cycleDistance) steered by the // control-mapper demands (throttle/turn), not a raw velocity; // * heading integration (rotate localOrigin.angularPosition by the turn rate); // * the terrain-height drop (BoundingBoxTreeNode::FindBoundingBoxUnder) that // rests the feet on the ground; // * the cockpit telemetry FilteredScalars (head/aim/leg/torso angular rates). // // With no locomotion layer yet, worldLinearVelocity is zero for a freshly // spawned mech, so it holds its pose -- identical to the prior DoNothing, but // now on the real Simulate path. DEV hook BT_DRIVE="vx,vy,vz" injects a // constant world velocity so the integrate + transform path is verifiable // headlessly (no RIO/controls needed). See MECH.NOTES.md. //############################################################################# // void Mech::Simulate(Scalar time_slice) { Check(this); // //----------------------------------------------------------------------- // Read the control-mapper locomotion demands. The mapper lives at roster // slot 0 and its InterpretControls Performance ticks in the Entity::Perform // AndWatch roster walk BEFORE this (the mech's own Performance runs last), // so speedDemand/turnDemand are this frame's. //----------------------------------------------------------------------- // Scalar speedDemand = 0.0f; Scalar turnDemand = 0.0f; if (subsystemArray != NULL && subsystemArray[0] != NULL) { MechControlsMapper *mapper = (MechControlsMapper *)subsystemArray[0]; speedDemand = mapper->GetSpeedDemand(); turnDemand = mapper->GetTurnDemand(); } bodyTargetSpeed = speedDemand; // //----------------------------------------------------------------------- // Accelerate the actual body speed toward the demand (bounded per frame by // the mech's max acceleration). //----------------------------------------------------------------------- // { Scalar dv = bodyTargetSpeed - currentBodySpeed; Scalar maxStep = maxBodyAcceleration * time_slice; if (dv > maxStep) dv = maxStep; if (dv < -maxStep) dv = -maxStep; currentBodySpeed += dv; } // //----------------------------------------------------------------------- // Authentic per-mech turn rate: lerp(walkingTurnRate, runningTurnRate) by // ground speed, with a runningTurnRate/t^2 over-run falloff past top speed; // clamp >= 0. (mech4.cpp master-perf @0x4aa3d3.) //----------------------------------------------------------------------- // Scalar authTurnRate = walkingTurnRate; { Scalar spd = (currentBodySpeed < 0.0f) ? -currentBodySpeed : currentBodySpeed; if (spd >= reverseSpeedMax) { Scalar den = reverseStrideLength - walkStrideLength; Scalar t = (den != 0.0f) ? (spd - walkStrideLength) / den : 0.0f; if (t <= 1.0f) { authTurnRate = walkingTurnRate + (runningTurnRate - walkingTurnRate) * t; } else { authTurnRate = runningTurnRate / (t * t); } } if (authTurnRate < 0.0f) { authTurnRate = 0.0f; } } // //----------------------------------------------------------------------- // Integrate heading (yaw) into the body orientation quaternion via the // engine's rotation-integrate op (Quaternion::Add(source, omega*dt)), then // rebuild the world transform so the facing axis below is current. //----------------------------------------------------------------------- // { Vector3D angStep; angStep.x = 0.0f; angStep.y = turnDemand * authTurnRate * time_slice; angStep.z = 0.0f; Quaternion prevPose = localOrigin.angularPosition; localOrigin.angularPosition.Add(prevPose, angStep); } localToWorld = localOrigin; // //----------------------------------------------------------------------- // Forward step: the mech faces local -Z (gun ports / eyepoint at -Z). Take // the world Z basis and negate for the facing direction; move at the current // body speed. (The animation-exact per-frame advance from the gait clip is // the deferred fidelity layer; this is the procedural equivalent.) //----------------------------------------------------------------------- // UnitVector zAxis; localToWorld.GetFromAxis(Z_Axis, &zAxis); worldLinearVelocity.x = -zAxis.x * currentBodySpeed; worldLinearVelocity.y = -zAxis.y * currentBodySpeed; worldLinearVelocity.z = -zAxis.z * currentBodySpeed; // // DEV override: BT_DRIVE forces a raw world velocity (bypasses the demands, // for the pure integrate/transform test). // { const char *drive = getenv("BT_DRIVE"); if (drive != NULL) { float dx = 0.0f, dy = 0.0f, dz = 0.0f; if (sscanf(drive, "%f,%f,%f", &dx, &dy, &dz) == 3) { worldLinearVelocity.x = dx; worldLinearVelocity.y = dy; worldLinearVelocity.z = dz; } } } // //----------------------------------------------------------------------- // Eyepoint / aim-ray composition (mech4.cpp @~5219, pixel-calibrated in the // BT411 reverse-engineering). The pilot's torso-elevation aim does NOT tilt // any skeleton joint on this mech family -- it pitches the cockpit eye and // the weapon boresight directly. Compose the committed look-state pitch/yaw // (CommitLookState, driven by the look buttons) with the Torso's live // currentElevation. DPLEyeRenderable / the aim ray read this each frame. //----------------------------------------------------------------------- // { Scalar elevation = 0.0f; if (sinkSourceSubsystem != NULL) { elevation = ((Torso *)sinkSourceSubsystem)->CurrentElevation(); } eyepointRotation = EulerAngles( Radian(Radian::Normalize(lookPitch + elevation)), Radian(Radian::Normalize(lookYaw)), Radian(0.0f) ); } // //----------------------------------------------------------------------- // Integrate position and commit the Origin to the world transform. //----------------------------------------------------------------------- // localOrigin.linearPosition.AddScaled( localOrigin.linearPosition, worldLinearVelocity, time_slice ); localToWorld = localOrigin; if (getenv("BT_MECH_LOG")) { static Scalar reportAccum = 0.0f; reportAccum += time_slice; if (reportAccum >= 1.0f) { reportAccum = 0.0f; EulerAngles ypr; ypr = localOrigin.angularPosition; DEBUG_STREAM << "[sim] pos=(" << localOrigin.linearPosition.x << "," << localOrigin.linearPosition.y << "," << localOrigin.linearPosition.z << ")" << " yaw=" << (Scalar)ypr.yaw << " spd=" << currentBodySpeed << " eyePitch=" << (Scalar)eyepointRotation.pitch << " eyeYaw=" << (Scalar)eyepointRotation.yaw << endl << flush; } } Check_Fpu(); }