public override void PreCollisionTestUpdate(GameThing thing) { Movement movement = thing.Movement; DesiredMovement desiredMovement = thing.DesiredMovement; GameActor.State state = thing.CurrentState; //string DebugString = "------------- Debug Fish: "+ thing.CreatableName +" --------------"; float secs = Time.GameTimeFrameSeconds; // Only do movement if we are not being held and active. if (state == GameActor.State.Active && thing.ActorHoldingThis == null) { // Test for grounding. float terrainAltitude = Terrain.GetHeight(movement.Position); float waterBaseAltitude = Terrain.GetWaterBase(movement.Position); Vector3 waterNormal = Vector3.UnitZ; float waterAltitude = (waterBaseAltitude > 0) ? Terrain.GetWaterHeightAndNormal(movement.Position, ref waterNormal) : 0.0f; // Note that grounded tells us if there's too little water to float. // grounded should be false if there's no terrain under the bot. float groundedAmount = terrainAltitude - waterAltitude - (ReScale * HullDraft); bool grounded = terrainAltitude != 0 && (waterAltitude == 0.0f || groundedAmount > 0.0f); Vector3 groundedNormal = grounded ? Terrain.GetNormal(movement.Position) : Vector3.UnitZ; // Undo wave offset. movement.Position -= new Vector3(0, 0, waveOffset); if (!grounded) { // Apply DesiredMovement values to current movement. ApplyDesiredMovement(movement, desiredMovement); bodyFlex = movement.RotationZRate / TurningSpeedModifier * 0.5f; float newFlexAmp = MaxRotationRate - Math.Abs(movement.RotationZRate / TurningSpeedModifier); flexAmplitude = MyMath.Lerp(flexAmplitude, newFlexAmp * newFlexAmp, secs); // Wiggle less when turning. // Apply drag to velocity. ApplyFriction(movement, desiredMovement, applyVertical: true); } // end if not grounded. // Now see if we're stuck or hitting shore. if (grounded && !(jumping || landing)) { Vector3 terrainNormal = groundedNormal; // Grounded tells us there's too little water to float, now check if we're // actually touching the ground. float closeToGround = 0.1f; if (movement.Position.Z - hullDraft - terrainAltitude < closeToGround) { // Slide if shallow. if (groundedAmount < 1.0f) { // Slide down hill a bit. terrainNormal.Z = 0.0f; movement.Position += 10.0f * terrainNormal * secs; movement.Velocity = 10.0f * terrainNormal * secs; } else { // High and dry movement.Velocity = Vector3.Zero; } } else { // Decay any horizontal velocity. float friction = GameActor.FrictionDecay(Friction, secs); movement.Velocity *= new Vector3(friction, friction, 1); } } // Move. if (state != GameThing.State.Paused) { // Apply external force. movement.Velocity += desiredMovement.ExternalForce.GetValueOrDefault() / thing.Mass * secs; movement.Position += movement.Velocity * secs; } // Adjust pitch based on vertical movement. float deltaVZ = movement.Altitude - movement.PrevPosition.Z; if (deltaVZ > 0.01f) { pitch = MyMath.Lerp(pitch, MaxPitch, secs); } else if (deltaVZ < -0.01f) { pitch = MyMath.Lerp(pitch, -MaxPitch, secs); } else { pitch = MyMath.Lerp(pitch, 0, secs); } // At the new position, // push bot down due to being above the surface. // push bot up to keep out of mud. // calc waveOffset // calc waveEffect based on how close to surface we are combined with how grounded we are. waterBaseAltitude = Terrain.GetWaterBase(movement.Position); terrainAltitude = Terrain.GetHeight(movement.Position); waterAltitude = (waterBaseAltitude > 0) ? Terrain.GetWaterHeightAndNormal(movement.Position, ref waterNormal) : 0.0f; // Keep bot out of mud. // Calc how deep we are and push us back up. // Need to zero out velocity.Z when we do this. float inMud = (movement.Position.Z + waveOffset - ReScale * HullDraft) - terrainAltitude; if (inMud < 0.0f && terrainAltitude > 0) { movement.Position -= new Vector3(0, 0, inMud); movement.Velocity *= new Vector3(1, 1, 0); } else { // Push bot down if too high at surface (or even in the air). // Only apply this if not inMud. if (movement.Position.Z > waterAltitude - MinDepth || terrainAltitude == 0.0f) { movement.Velocity += new Vector3(0, 0, Gravity * secs); } } // Calc wave effect. We only want the waves to have an effect when the bot is near // the surface. If grounded, then we want the bot to have a chance to slide back // into the water. If just near the surface, the bot should move with the waves. float waveEffect = 0.0f; if (grounded) { waveEffect = 1.0f - groundedAmount; } else { // Make the waveEffect strongest at the surface and fading to 0 as you go deeper. float maxWaveEffectDepth = 3.0f; float depth = waterBaseAltitude - movement.Position.Z; if (depth > maxWaveEffectDepth) { waveEffect = 0.0f; } else { waveEffect = (maxWaveEffectDepth - depth) / maxWaveEffectDepth; } } waveEffect = MathHelper.Clamp(waveEffect, 0.0f, 1.0f); waveOffset = waveEffect * (waterAltitude - waterBaseAltitude); if (grounded) { // Blend between ground normal and wave normal. This allows some minimal // rocking until we're fully grounded. groundedNormal = MyMath.Lerp(groundedNormal, waterNormal, waveEffect); botNormal = MyMath.Lerp(botNormal, groundedNormal, secs); } else { // Blend between vertical and wave normal. This allows the waves to // affect the bot more when it's close to the surface. waterNormal = MyMath.Lerp(Vector3.UnitZ, waterNormal, waveEffect); botNormal = MyMath.Lerp(botNormal, waterNormal, secs); CheckRipples(thing, waveEffect); } // If in edit mode kill any velocity. if (state == GameActor.State.Paused) { movement.Velocity = Vector3.Zero; } // Create a matrix orienting the actor with the surface it's on. botNormal.Normalize(); Vector3 forward = new Vector3(1.0f, 0.0f, 0.0f); Vector3 left = Vector3.Cross(botNormal, forward); forward = Vector3.Cross(left, botNormal); Matrix local = Matrix.Identity; // Ok, this looks strange. This is because XNA GS assumes Y is up and -Z is forward. Argh. local.Backward = botNormal; local.Up = left; local.Right = forward; // Add in Z rotation and pitch. local = Matrix.CreateRotationY(-pitch) * Matrix.CreateRotationZ(movement.RotationZ) * local; // And translate to the right place. local.Translation = movement.Position; // Add in wave offset. local.M43 = movement.Position.Z + waveOffset; // Finally, set the local matrix explicitly movement.SetLocalMatrixAndRotation(local, movement.RotationZ); } //end of If State == Active && State != Held //Debug.WriteLine( DebugString ); } // end of PreCollisionTestUpdate()