public void Process(double dt, PhysicsStateData currentState, SceneInterface scene, double mass, double gravityConstant) { if (m_Params.VehicleType == VehicleType.None) { /* disable vehicle */ m_Params[VehicleFloatParamId.HeightExceededTime] = 0; return; } try { VehicleFlags flags = m_Params.Flags; Vector3 linearBodyForce = Vector3.Zero; Vector3 angularBodyTorque = Vector3.Zero; #region Transform Reference Frame Quaternion rotationalReferenceFrame = m_Params[VehicleRotationParamId.ReferenceFrame]; Quaternion linearReferenceFrame = currentState.Rotation / rotationalReferenceFrame; Vector3 velocity = currentState.Velocity / linearReferenceFrame; Vector3 angularVelocity = currentState.AngularVelocity / rotationalReferenceFrame; Quaternion angularOrientation = currentState.Rotation / rotationalReferenceFrame; #endregion Vector3 mouselookAngularInput = Vector3.Zero; if ((flags & (VehicleFlags.MouselookBank | VehicleFlags.MouselookSteer | VehicleFlags.MousePointBank | VehicleFlags.MousePointSteer)) != 0) { Quaternion localCam = currentState.CameraRotation / rotationalReferenceFrame; mouselookAngularInput = (localCam / angularOrientation).GetEulerAngles(); mouselookAngularInput.Y = 0; mouselookAngularInput.X = (IsMouselookBankActive(flags, currentState)) ? mouselookAngularInput.Z * m_Params[VehicleFloatParamId.MouselookAltitude] : 0; if (IsMouselookSteerActive(flags, currentState)) { mouselookAngularInput.Z *= m_Params[VehicleFloatParamId.MouselookAzimuth]; } else { mouselookAngularInput.Z = 0; } } #region MoveToTarget inputs Vector3 targetPos = Vector3.Zero; Vector3 linearTargetDirection = Vector3.Zero; Vector3 angularTargetDirection = Vector3.Zero; TargetList targetList = m_TargetList; if (targetList?.TryGetTarget(out targetPos) ?? false) { Vector3 linearError = targetPos - currentState.Position; Vector3 angularError = Quaternion.RotBetween(Vector3.UnitX, linearError).GetEulerAngles(); Vector3 linearEpsilon = m_Params[VehicleVectorParamId.LinearMoveToTargetEpsilon]; Vector3 angularEpsilon = m_Params[VehicleVectorParamId.AngularMoveToTargetEpsilon]; Vector3 linearMaxOutput = m_Params[VehicleVectorParamId.LinearMoveToTargetMaxOutput]; Vector3 angularMaxOutput = m_Params[VehicleVectorParamId.AngularMoveToTargetMaxOutput]; bool linearTargetReached = (Math.Abs(linearError.X) < linearEpsilon.X || linearEpsilon.X < 0) && (Math.Abs(linearError.Y) < linearEpsilon.Y || linearEpsilon.Y < 0) && (Math.Abs(linearError.Z) < linearEpsilon.Z || linearEpsilon.Z < 0); bool angularTargetReached = (Math.Abs(angularError.X) < angularEpsilon.X || angularEpsilon.X < 0) && (Math.Abs(angularError.Y) < angularEpsilon.Y || angularEpsilon.Y < 0) && (Math.Abs(angularError.Z) < angularEpsilon.Z || angularEpsilon.Z < 0); /* limiters */ linearError = linearError.ComponentClamp(-linearMaxOutput, linearMaxOutput); angularError = angularError.ComponentClamp(-angularMaxOutput, angularMaxOutput); /* timescalers */ linearTargetDirection = linearError.ElementMultiply((m_Params[VehicleVectorParamId.LinearMoveToTargetEfficiency].ElementMultiply(m_Params.OneByLinearMoveToTargetTimescale) * dt).ComponentMin(1)); angularTargetDirection = angularError.ElementMultiply((m_Params[VehicleVectorParamId.AngularMoveToTargetEfficiency].ElementMultiply(m_Params.OneByAngularMoveToTargetTimescale) * dt).ComponentMin(1)); if (linearTargetReached && angularTargetReached && !m_TargetList.NextTarget() && (m_Params.Flags & VehicleFlags.StopMoveToTargetAtEnd) != 0) { /* remove target list if not already changed */ Interlocked.CompareExchange(ref m_TargetList, null, targetList); } } #endregion #region Motor Inputs Vector3 linearMotorDirection = m_Params[VehicleVectorParamId.LinearMotorDirection] + linearTargetDirection; Vector3 angularMotorDirection = m_Params[VehicleVectorParamId.AngularMotorDirection] + mouselookAngularInput + angularTargetDirection; Vector3 linearMotorError = linearMotorDirection - velocity; Vector3 angularMotorError = angularMotorDirection - angularVelocity; Vector3 oneByLinearMotorTimescale = Vector3.Zero; Vector3 oneByAngularMotorTimescale = Vector3.Zero; #region Linear motor timescales Vector3 oneByLinearMotorAccelPosTimescale = m_Params.OneByLinearMotorAccelPosTimescale; Vector3 oneByLinearMotorDecelPosTimescale = m_Params.OneByLinearMotorDecelPosTimescale; Vector3 oneByLinearMotorAccelNegTimescale = m_Params.OneByLinearMotorAccelNegTimescale; Vector3 oneByLinearMotorDecelNegTimescale = m_Params.OneByLinearMotorDecelNegTimescale; if (linearMotorDirection.X * velocity.X < 0 && Math.Abs(linearMotorDirection.X) > double.Epsilon) { oneByLinearMotorTimescale.X = linearMotorDirection.X < 0 ? oneByLinearMotorAccelNegTimescale.X : oneByLinearMotorAccelPosTimescale.X; } else if (Math.Abs(linearMotorDirection.X) <= double.Epsilon) { oneByLinearMotorTimescale.X = velocity.X < 0 ? oneByLinearMotorDecelNegTimescale.X : oneByLinearMotorDecelPosTimescale.X; } else if (linearMotorDirection.X >= 0) { oneByLinearMotorTimescale.X = linearMotorError.X * velocity.X < 0 ? oneByLinearMotorDecelPosTimescale.X : oneByLinearMotorAccelPosTimescale.X; } else { oneByLinearMotorTimescale.X = linearMotorError.X * velocity.X < 0 ? oneByLinearMotorDecelNegTimescale.X : oneByLinearMotorAccelNegTimescale.X; } if (linearMotorDirection.Y * velocity.Y < 0 && Math.Abs(linearMotorDirection.Y) > double.Epsilon) { oneByLinearMotorTimescale.Y = linearMotorDirection.Y < 0 ? oneByLinearMotorAccelNegTimescale.Y : oneByLinearMotorAccelPosTimescale.Y; } else if (Math.Abs(linearMotorDirection.Y) <= double.Epsilon) { oneByLinearMotorTimescale.Y = velocity.Y < 0 ? oneByLinearMotorDecelNegTimescale.Y : oneByLinearMotorDecelPosTimescale.Y; } else if (linearMotorDirection.Y >= 0) { oneByLinearMotorTimescale.Y = linearMotorError.Y * velocity.Y < 0 ? oneByLinearMotorDecelPosTimescale.Y : oneByLinearMotorAccelPosTimescale.Y; } else { oneByLinearMotorTimescale.Y = linearMotorError.Y * velocity.Y < 0 ? oneByLinearMotorDecelNegTimescale.Y : oneByLinearMotorAccelNegTimescale.Y; } if (linearMotorDirection.Z * velocity.Z < 0 && Math.Abs(linearMotorDirection.Z) > double.Epsilon) { oneByLinearMotorTimescale.Z = linearMotorDirection.Z < 0 ? oneByLinearMotorAccelNegTimescale.Z : oneByLinearMotorAccelPosTimescale.Z; } else if (Math.Abs(linearMotorDirection.Z) <= double.Epsilon) { oneByLinearMotorTimescale.Z = velocity.Z < 0 ? oneByLinearMotorDecelNegTimescale.Z : oneByLinearMotorDecelPosTimescale.Z; } else if (linearMotorDirection.Z >= 0) { oneByLinearMotorTimescale.Z = linearMotorError.Z * velocity.Z < 0 ? oneByLinearMotorDecelPosTimescale.Z : oneByLinearMotorAccelPosTimescale.Z; } else { oneByLinearMotorTimescale.Z = linearMotorError.Z * velocity.Z < 0 ? oneByLinearMotorDecelNegTimescale.Z : oneByLinearMotorAccelNegTimescale.Z; } #endregion #region Angular motor timescales Vector3 oneByAngularMotorAccelPosTimescale = m_Params.OneByAngularMotorAccelPosTimescale; Vector3 oneByAngularMotorDecelPosTimescale = m_Params.OneByAngularMotorDecelPosTimescale; Vector3 oneByAngularMotorAccelNegTimescale = m_Params.OneByAngularMotorAccelNegTimescale; Vector3 oneByAngularMotorDecelNegTimescale = m_Params.OneByAngularMotorDecelNegTimescale; if (angularMotorDirection.X * angularVelocity.X < 0 && Math.Abs(angularMotorDirection.X) > double.Epsilon) { oneByAngularMotorTimescale.X = angularMotorDirection.X < 0 ? oneByAngularMotorAccelNegTimescale.X : oneByAngularMotorAccelPosTimescale.X; } else if (Math.Abs(angularMotorDirection.X) <= double.Epsilon) { oneByAngularMotorTimescale.X = angularVelocity.X < 0 ? oneByAngularMotorDecelNegTimescale.X : oneByAngularMotorDecelPosTimescale.X; } else if (angularMotorDirection.X >= 0) { oneByAngularMotorTimescale.X = angularMotorError.X * angularVelocity.X < 0 ? oneByAngularMotorDecelPosTimescale.X : oneByAngularMotorAccelPosTimescale.X; } else { oneByAngularMotorTimescale.X = angularMotorError.X * angularVelocity.X < 0 ? oneByAngularMotorDecelNegTimescale.X : oneByAngularMotorAccelNegTimescale.X; } if (angularMotorDirection.Y * angularVelocity.Y < 0 && Math.Abs(angularMotorDirection.Y) > double.Epsilon) { oneByAngularMotorTimescale.Y = angularMotorDirection.Y < 0 ? oneByAngularMotorAccelNegTimescale.Y : oneByAngularMotorAccelPosTimescale.Y; } else if (Math.Abs(angularMotorDirection.Y) <= double.Epsilon) { oneByAngularMotorTimescale.Y = angularVelocity.Y < 0 ? oneByAngularMotorDecelNegTimescale.Y : oneByAngularMotorDecelPosTimescale.Y; } else if (angularMotorDirection.Y >= 0) { oneByAngularMotorTimescale.Y = angularMotorError.Y * angularVelocity.Y < 0 ? oneByAngularMotorDecelPosTimescale.Y : oneByAngularMotorAccelPosTimescale.Y; } else { oneByAngularMotorTimescale.Y = angularMotorError.Y * angularVelocity.Y < 0 ? oneByAngularMotorDecelNegTimescale.Y : oneByAngularMotorAccelNegTimescale.Y; } if (angularMotorDirection.Z * angularVelocity.Z < 0 && Math.Abs(angularMotorDirection.Z) > double.Epsilon) { oneByAngularMotorTimescale.Z = angularMotorDirection.Z < 0 ? oneByAngularMotorAccelNegTimescale.Z : oneByAngularMotorAccelPosTimescale.Z; } else if (Math.Abs(angularMotorDirection.Z) <= double.Epsilon) { oneByAngularMotorTimescale.Z = angularVelocity.Z < 0 ? oneByAngularMotorDecelNegTimescale.Z : oneByAngularMotorDecelPosTimescale.Z; } else if (angularMotorDirection.Z >= 0) { oneByAngularMotorTimescale.Z = angularMotorError.Z * angularVelocity.Z < 0 ? oneByAngularMotorDecelPosTimescale.Z : oneByAngularMotorAccelPosTimescale.Z; } else { oneByAngularMotorTimescale.Z = angularMotorError.Z * angularVelocity.Z < 0 ? oneByAngularMotorDecelNegTimescale.Z : oneByAngularMotorAccelNegTimescale.Z; } #endregion linearBodyForce += LinearMotorForce = linearMotorError.ElementMultiply(oneByLinearMotorTimescale * dt); angularBodyTorque += AngularMotorTorque = angularMotorError.ElementMultiply(oneByAngularMotorTimescale * dt); if ((m_Params.Flags & VehicleFlags.TorqueWorldZ) != 0) { /* translate Z to world (needed for motorcycles based on halcyon design) */ double angZ = angularBodyTorque.Z; angularBodyTorque.Z = 0; Quaternion q = Quaternion.CreateFromEulers(0, 0, angZ); angularBodyTorque += WorldZTorque = (q * angularOrientation).GetEulerAngles(); } #endregion #region Motor Limiting if ((m_Params.Flags & VehicleFlags.LimitMotorDown) != 0 && linearBodyForce.Z < 0) { linearBodyForce.Z = 0; } if ((m_Params.Flags & VehicleFlags.LimitMotorUp) != 0 && linearBodyForce.Z > 0) { linearBodyForce.Z = 0; } #endregion #region Hover Height Influence Calculation double hoverForce; Vector3 pos = currentState.Position; double paramHoverHeight = m_Params[VehicleFloatParamId.HoverHeight]; double hoverHeight = 0; double terrainHeight = scene.Terrain[pos]; double waterHeight = scene.RegionSettings.WaterHeight; if ((flags & VehicleFlags.HoverGlobalHeight) != 0) { hoverHeight = Math.Max(paramHoverHeight, terrainHeight); } else { paramHoverHeight = Math.Min(100, paramHoverHeight); hoverHeight = terrainHeight + paramHoverHeight; double waterHoverHeight = waterHeight + paramHoverHeight; if ((flags & VehicleFlags.HoverWaterOnly) != 0 || ((flags & VehicleFlags.HoverTerrainOnly) == 0 && hoverHeight < waterHoverHeight)) { hoverHeight = waterHoverHeight; } } if (m_Params.IsHoverMotorEnabled) { /* The definition does not include negative hover height. * But since we are allowing negative terain height, it makes an useful feature. */ double hoverVelocity = (hoverHeight + 0.21728 - pos.Z) - velocity.Z; if ((m_Params.Flags & VehicleFlags.HoverUpOnly) != 0 && hoverVelocity < 0) { hoverVelocity = 0; } hoverForce = hoverVelocity * m_Params[VehicleFloatParamId.HoverEfficiency] * m_Params.OneByHoverTimescale * dt; } else { hoverForce = 0; } #endregion #region Disable Motor Logic (neat idea based on halcyon simulator) double disableMotorsAfter = m_Params[VehicleFloatParamId.DisableMotorsAfter]; double heightExceededTime = m_Params[VehicleFloatParamId.HeightExceededTime]; if (disableMotorsAfter > double.Epsilon && m_Params[VehicleFloatParamId.DisableMotorsAbove] < pos.Z - hoverHeight) { heightExceededTime += dt; m_Params[VehicleFloatParamId.HeightExceededTime] = dt; if (disableMotorsAfter <= heightExceededTime) { angularBodyTorque = Vector3.Zero; linearBodyForce = Vector3.Zero; } } else { m_Params[VehicleFloatParamId.HeightExceededTime] = 0; } #endregion #region Friction linearBodyForce += LinearFrictionForce = (-currentState.Velocity).ElementMultiply((m_Params.OneByLinearFrictionTimescale * dt).ComponentMin(1)); angularBodyTorque += AngularFrictionTorque = (-currentState.AngularVelocity).ElementMultiply((m_Params.OneByAngularFrictionTimescale * dt).ComponentMin(1)); #endregion #region Vertical Attractor /* vertical attractor is a angular motor * VEHICLE_FLAG_LIMIT_ROLL_ONLY affects this one to be only affected on roll axis */ Vector3 vaTimescale = m_Params[VehicleVectorParamId.VerticalAttractionTimescale]; double roll = 0; if (vaTimescale.X < VerticalAttractorTimescaleDisable || vaTimescale.Y < VerticalAttractorTimescaleDisable) { Vector3 angularError = angularOrientation.GetNauticalAngles(); angularError.Z = 0; if (angularError.X > Math.PI) { angularError.X -= Math.PI * 2; } if (angularError.Y > Math.PI) { angularError.Y -= Math.PI * 2; } roll = angularError.X; angularError = -angularError - angularVelocity; Vector3 vertAttractorTorque = angularError.ElementMultiply((m_Params[VehicleVectorParamId.VerticalAttractionEfficiency].ElementMultiply(m_Params.OneByVerticalAttractionTimescale) * dt).ComponentMin(1)); if (vaTimescale.X > VerticalAttractorTimescaleDisable) { vertAttractorTorque.X = 0; } if ((flags & VehicleFlags.LimitRollOnly) != 0 || vaTimescale.Y > VerticalAttractorTimescaleDisable) { vertAttractorTorque.Y = 0; } angularBodyTorque += vertAttractorTorque; VerticalAttractorTorque = vertAttractorTorque; } #endregion if ((flags & (VehicleFlags.ReactToWind | VehicleFlags.ReactToCurrents)) != 0) { double windCurrentMix; double halfBoundBoxSizeZ = (currentState.BoundBox.Size * rotationalReferenceFrame).Z / 2; if (pos.Z - halfBoundBoxSizeZ > waterHeight || currentState.BoundBox.Size.Z < double.Epsilon) { windCurrentMix = 1; } else if (pos.Z + halfBoundBoxSizeZ < waterHeight) { windCurrentMix = 0; } else { windCurrentMix = (pos.Z - halfBoundBoxSizeZ - waterHeight) / currentState.BoundBox.Size.Z; } if ((flags & VehicleFlags.ReactToWind) != 0 && pos.Z + halfBoundBoxSizeZ > waterHeight) { Vector3 windvelocity = scene.Environment.Wind[pos + new Vector3(0, 0, halfBoundBoxSizeZ / 2)] / linearReferenceFrame; #region Linear Wind Affector linearBodyForce += LinearWindForce = (windvelocity - velocity).ElementMultiply(m_Params[VehicleVectorParamId.LinearWindEfficiency]) * dt; #endregion #region Angular Wind Affector windvelocity = new Vector3(-windvelocity.Y, windvelocity.X, 0); if (angularVelocity.X * windvelocity.X >= 0 && angularVelocity.X.PosIfNotNeg() * (angularVelocity.X - windvelocity.X) > 0) { windvelocity.X = 0; } if (angularVelocity.Y * windvelocity.Y >= 0 && angularVelocity.Y.PosIfNotNeg() * (angularVelocity.Y - windvelocity.Y) > 0) { windvelocity.Y = 0; } angularBodyTorque += AngularWindTorque = windvelocity.ElementMultiply(m_Params[VehicleVectorParamId.AngularWindEfficiency]) * dt * windCurrentMix; #endregion } if ((flags & VehicleFlags.ReactToCurrents) != 0 && pos.Z - halfBoundBoxSizeZ / 2 < waterHeight) { /* yes, wind model also provides current model */ Vector3 currentvelocity = scene.Environment.Wind[pos - new Vector3(0, 0, -halfBoundBoxSizeZ / 2)] / linearReferenceFrame; #region Linear Current Affector linearBodyForce += LinearCurrentForce = (currentvelocity - velocity).ElementMultiply(m_Params[VehicleVectorParamId.LinearCurrentEfficiency]) * dt; #endregion #region Angular Current Affector /* works opposite to wind as we are simulating its attacking force below center */ currentvelocity = new Vector3(currentvelocity.Y, -currentvelocity.X, 0); if (angularVelocity.X * currentvelocity.X >= 0 && angularVelocity.X.PosIfNotNeg() * (angularVelocity.X - currentvelocity.X) > 0) { currentvelocity.X = 0; } if (angularVelocity.Y * currentvelocity.Y >= 0 && angularVelocity.Y.PosIfNotNeg() * (angularVelocity.Y - currentvelocity.Y) > 0) { currentvelocity.Y = 0; } angularBodyTorque += AngularCurrentTorque = currentvelocity.ElementMultiply(m_Params[VehicleVectorParamId.AngularCurrentEfficiency]) * dt * (1 - windCurrentMix); #endregion } } #region Banking Motor if (m_Params[VehicleVectorParamId.VerticalAttractionTimescale].X < 300) { double invertedBankModifier = 1f; if ((Vector3.UnitZ * angularOrientation).Z < 0) { invertedBankModifier = m_Params[VehicleFloatParamId.InvertedBankingModifier]; } angularBodyTorque.Z += BankingTorque = (roll * 1.0.Mix(velocity.X, m_Params[VehicleFloatParamId.BankingMix])) * m_Params[VehicleFloatParamId.BankingEfficiency] * invertedBankModifier * m_Params.OneByBankingTimescale * dt; } #endregion #region Angular Deflection /* Angular deflection reorients the vehicle to the velocity vector */ Vector3 deflect = velocity; if (deflect.X < 0) { deflect = -deflect; } if (Math.Abs(deflect.X) < 0.01) { deflect.X = 0.01; deflect.Y = velocity.Y; deflect.Z = velocity.Z; } Vector3 angdeflecteff = (m_Params[VehicleVectorParamId.AngularDeflectionEfficiency].ElementMultiply(m_Params.OneByAngularDeflectionTimescale) * dt).ComponentMin(1); Vector3 angularDeflectionTorque = Vector3.Zero; if (Math.Abs(deflect.Z) > 0.01) { angularDeflectionTorque.Y = Math.Atan2(deflect.Z, deflect.X) * angdeflecteff.Y; } if (Math.Abs(deflect.Y) > 0.01) { angularDeflectionTorque.Z = Math.Atan2(deflect.Y, deflect.X) * angdeflecteff.Z; } AngularDeflectionTorque = angularDeflectionTorque; angularBodyTorque += angularDeflectionTorque; #endregion #region Linear Deflection /* Linear deflection deflects the affecting force along the reference x-axis */ var eulerDiff = new Vector3 { Z = Math.Atan2(velocity.Y, velocity.X) }; if ((flags & VehicleFlags.NoDeflectionUp) != 0) { eulerDiff.Y = Math.Atan2(velocity.Z, velocity.X); } eulerDiff = eulerDiff.ElementMultiply((m_Params[VehicleVectorParamId.LinearDeflectionEfficiency].ElementMultiply(m_Params.OneByLinearDeflectionTimescale) * dt).ComponentMin(1)); linearBodyForce += LinearDeflectionForce = velocity * Quaternion.CreateFromEulers(eulerDiff) - velocity; #endregion #region Motor Decay m_Params.DecayDirections(dt); #endregion #region Buoyancy /* we simply act against the physics effect of the BuoyancyMotor */ linearBodyForce.Z += m_Params[VehicleFloatParamId.Buoyancy] * mass * gravityConstant; #endregion #region Add Hover Height Force after rotating body force linearBodyForce = linearBodyForce * linearReferenceFrame; linearBodyForce.Z += HoverMotorForce = hoverForce; #endregion if (!linearBodyForce.IsNaN) { LinearForce = linearBodyForce; } if (!angularBodyTorque.IsNaN) { AngularTorque = angularBodyTorque * rotationalReferenceFrame; } } catch { /* if we have exceptions here, just stop the vehicle motor */ LinearForce = Vector3.Zero; AngularTorque = Vector3.Zero; } }