public override void OnPlayerControlTick(Player owner) { base.OnPlayerControlTick(owner); //DebugTrace( owner ); return; if (!NavMesh.IsLoaded) { return; } timeSinceLast = 0; var forward = owner.EyeRot.Forward * 2000; var tr = Trace.Ray(owner.EyePos, owner.EyePos + forward) .Ignore(owner) .Run(); var closestPoint = NavMesh.GetClosestPoint(tr.EndPos); DebugOverlay.Line(tr.EndPos, closestPoint, 0.1f); DebugOverlay.Axis(closestPoint, Rotation.LookAt(tr.Normal), 2.0f, Time.Delta * 2); DebugOverlay.Text(closestPoint, $"CLOSEST Walkable POINT", Time.Delta * 2); NavMesh.BuildPath(Owner.WorldPos, closestPoint); }
public bool Raycast(float length, bool doPhysics, Vector3 offset, ref float wheel, float dt) { var position = parent.Position; var rotation = parent.Rotation; var wheelAttachPos = position + offset; var wheelExtend = wheelAttachPos - rotation.Up * (length * parent.Scale); var tr = Trace.Ray(wheelAttachPos, wheelExtend) .Ignore(parent) .Ignore(parent.Driver) .Run(); wheel = length * tr.Fraction; var wheelRadius = (14 * parent.Scale); if (!doPhysics && CarEntity.debug_car) { var wheelPosition = tr.Hit ? tr.EndPosition : wheelExtend; wheelPosition += rotation.Up * wheelRadius; if (tr.Hit) { DebugOverlay.Circle(wheelPosition, rotation * Rotation.FromYaw(90), wheelRadius, Color.Red.WithAlpha(0.5f), false); DebugOverlay.Line(tr.StartPosition, tr.EndPosition, Color.Red, 0, false); } else { DebugOverlay.Circle(wheelPosition, rotation * Rotation.FromYaw(90), wheelRadius, Color.Green.WithAlpha(0.5f), false); DebugOverlay.Line(wheelAttachPos, wheelExtend, Color.Green, 0, false); } } if (!tr.Hit || !doPhysics) { return(tr.Hit); } var body = parent.PhysicsBody.SelfOrParent; _previousLength = _currentLength; _currentLength = (length * parent.Scale) - tr.Distance; var springVelocity = (_currentLength - _previousLength) / dt; var springForce = body.Mass * 50.0f * _currentLength; var damperForce = body.Mass * (1.5f + (1.0f - tr.Fraction) * 3.0f) * springVelocity; var velocity = body.GetVelocityAtPoint(wheelAttachPos); var speed = velocity.Length; var speedDot = MathF.Abs(speed) > 0.0f ? MathF.Abs(MathF.Min(Vector3.Dot(velocity, rotation.Up.Normal) / speed, 0.0f)) : 0.0f; var speedAlongNormal = speedDot * speed; var correctionMultiplier = (1.0f - tr.Fraction) * (speedAlongNormal / 1000.0f); var correctionForce = correctionMultiplier * 50.0f * speedAlongNormal / dt; body.ApplyImpulseAt(wheelAttachPos, tr.Normal * (springForce + damperForce + correctionForce) * dt); return(true); }
protected void ReflectBall(CollisionEventData eventData, float multiplier) { var reflect = Vector3.Reflect(eventData.PreVelocity.Normal, eventData.Normal.Normal).Normal; var normalDot = eventData.PreVelocity.Normal.Dot(eventData.Normal); // Don't do any reflection if we hit it at such an angle if (normalDot <= 0.10) { return; } // Collision sound happens at this point, not entity var sound = Sound.FromWorld(BounceSound.Name, eventData.Pos); sound.SetVolume(0.2f + Math.Clamp(eventData.Speed / 1250.0f, 0.0f, 0.8f)); sound.SetPitch(0.5f + Math.Clamp(eventData.Speed / 1250.0f, 0.0f, 0.5f)); var particle = Particles.Create("particles/ball_hit.vpcf", eventData.Pos); particle.SetPos(0, eventData.Pos); particle.Destroy(false); var newSpeed = Math.Max(eventData.PreVelocity.Length, eventData.Speed); newSpeed *= multiplier; // Adjust the speed depending on the hit normal, slight hit = more speed newSpeed *= (1 - normalDot / 2); var newVelocity = reflect * newSpeed; // TODO: not a fan of this, should determine by the dot normal newVelocity.z = 0; PhysicsBody.Velocity = newVelocity; PhysicsBody.AngularVelocity = Vector3.Zero; if (Debug) { DebugOverlay.Text(eventData.Pos, $"V {eventData.PreVelocity.Length} -> {newSpeed}", 5f); DebugOverlay.Text(eventData.Pos + Vector3.Up * 8, $"N. {normalDot}", 5f); DebugOverlay.Line(eventData.Pos, eventData.Pos - (eventData.PreVelocity.Normal * 64.0f), 5f); DebugOverlay.Line(eventData.Pos, eventData.Pos + (reflect * 64.0f), 5f); } }
public virtual void PreTick() { if (!CanAttach()) { Reset(); return; } Hits = new TraceResult[DirectionsOfTravel.Length]; for (int i = 0; i < DirectionsOfTravel.Length; i++) { // Translate local direction to world space Vector3 direction = Controller.Rotation * DirectionsOfTravel[i]; Vector3 origin = Controller.Position + Vector3.Up * 5f; var tr = Trace.Ray(origin, origin + direction * WallMaxDistance) .Ignore(Controller.Pawn) .Run(); // Cache result Hits[i] = tr; if (Hits[i].Entity != null) { DebugOverlay.Sphere(Hits[i].EndPos, 3, Color.Green); DebugOverlay.Line(Controller.Position, origin + direction * WallMaxDistance, Color.Green); } else { DebugOverlay.Line(Controller.Position, origin + direction * WallMaxDistance, Color.Red); } } Hits = Hits.ToList().Where(h => h.Entity != null).OrderBy(h => h.Distance).ToArray(); if (Hits.Length > 0) { PerformWallRun(ref Hits[0]); } else { Reset(); } }
public void DebugTrace(Player player) { for (float x = -10; x < 10; x += 1.0f) { for (float y = -10; y < 10; y += 1.0f) { var tr = Trace.Ray(player.EyePos, player.EyePos + player.EyeRot.Forward * 4096 + player.EyeRot.Left * (x + Rand.Float(-1.6f, 1.6f)) * 100 + player.EyeRot.Up * (y + Rand.Float(-1.6f, 1.6f)) * 100).Ignore(player).Run(); if (IsServer) { DebugOverlay.Line(tr.EndPos, tr.EndPos + tr.Normal, Color.Cyan, duration: 20); } else { DebugOverlay.Line(tr.EndPos, tr.EndPos + tr.Normal, Color.Yellow, duration: 20); } } } }
public override void DoRender(SceneObject obj) { if (Power == 0.0f) { return; } Render.SetLighting(obj); var startPos = Position; var endPos = Position += Direction * Power * 100; var offset = Vector3.Cross(Direction, Vector3.Up) * (1 + 2 * Power); var trace = Trace.Ray(startPos, endPos); var result = trace.Run(); var remainingLength = (result.EndPos - endPos).Length; // Draw single arrow if no trace if (remainingLength.AlmostEqual(0.0f)) { var color = ColorConvert.HSLToRGB(120 - (int)(Power * Power * 120), 1.0f, 0.5f); DrawArrow(obj, startPos, endPos, Direction, offset, color, true); return; } // Draw two arrows var color2 = ColorConvert.HSLToRGB(120 - (int)(Power * Power * 120), 1.0f, 0.5f); DrawArrow(obj, startPos, result.EndPos, Direction, offset, color2, false); var direction2 = Vector3.Reflect(Direction, result.Normal); var endPos2 = result.EndPos + direction2 * remainingLength; var offset2 = Vector3.Cross(direction2, Vector3.Up) * (1 + 2 * Power); DrawArrow(obj, result.EndPos, endPos2, direction2, offset2, color2, true); DebugOverlay.Line(result.EndPos, endPos2); }
/// <summary> /// Shoot a single bullet /// </summary> public virtual void ShootBullet(float spread, float force, float damage, float bulletSize, float BulletCount = 0) { var forward = Owner.EyeRot.Forward; forward += (RandVec3(BulletCount) * spread * 0.25f); forward = forward.Normal; // // ShootBullet is coded in a way where we can have bullets pass through shit // or bounce off shit, in which case it'll return multiple results // foreach (var tr in TraceBullet(Owner.EyePos, Owner.EyePos + forward * 5000, bulletSize)) { BulletImpact(tr); DebugOverlay.Line(tr.StartPos, tr.EndPos, Host.IsServer ? Color.Yellow : Color.Blue, 5f); if (!IsServer) { continue; } if (!tr.Entity.IsValid()) { continue; } // // We turn prediction off for this, so any exploding effects don't get culled etc // using (Prediction.Off()) { var damageInfo = DamageInfo.FromBullet(tr.EndPos, forward * 100 * force, damage) .UsingTraceResult(tr) .WithAttacker(Owner) .WithWeapon(this); tr.Entity.TakeDamage(damageInfo); } } }
void PerformWallRun(ref TraceResult Hit) { float d = Vector3.Dot(Hit.Normal, Vector3.Up); if (d >= -NormalizedAngleThreshold && d <= NormalizedAngleThreshold) { // Vector3 alongWall = Vector3.Cross(hit.normal, Vector3.up); float vertical = 1; Vector3 alongWall = Controller.Rotation.Forward; DebugOverlay.Line(Controller.Position, Controller.Position + alongWall.Normal * 10f, Color.Green); DebugOverlay.Line(Controller.Position, LastWallNormal * 10, Color.Magenta); Controller.Velocity = alongWall * vertical * WallSpeedMultiplier; if (Activated > TimeUntilSlowDown) { Controller.Velocity += Vector3.Down * SlowDownSpeed; } if (Activated < TimeUntilStopClimbingUp) { float percent = Activated / TimeUntilStopClimbingUp; Controller.Velocity += Vector3.Up * (ClimbUpSpeed * Easing.EaseOut(percent)); } Activate(); } else { Reset(); } // Ensure this is at the end LastWallPosition = Hit.EndPos; LastWallNormal = Hit.Normal; }
public override void OnPlayerControlTick(TankPlayer player, bool allowDebug) { var extents = CollisionBounds.Maxs; var cannonEndLocal = extents.WithY(0) + Vector3.Down * 4; var cannonStart = player.Base.Position + cannonEndLocal.WithX(0); var cannonEnd = player.Base.Position + player.Head.Rotation * cannonEndLocal; var cannonTr = Trace.Ray(cannonStart, cannonEnd).Radius(4).WithoutTags("rocket").Run(); if (allowDebug) { DebugOverlay.Sphere(cannonTr.EndPos, 4, cannonTr.Hit ? Color.Red : Color.Green, true); DebugOverlay.Line(cannonStart, cannonEnd, Color.Blue, 0, false); } float retractTime = CannonCooldown * 0.1f, expandTime = CannonCooldown * 0.6f, cannonRecoilDistance = 16; float cannonRecoilOffset = 0; if (timeSinceCannonFire < retractTime) // Retracting { cannonRecoilOffset = cannonRecoilDistance * (timeSinceCannonFire / retractTime); } else if (timeSinceCannonFire < expandTime + retractTime) // Expanding { cannonRecoilOffset = cannonRecoilDistance - cannonRecoilDistance * ((timeSinceCannonFire - retractTime) / expandTime); } cannonRecoilOffset *= MathF.Pow(cannonTr.Distance / extents.x, 2); // Reduces recoil the more the cannon is pushed in due to collision float cannonOffset = (cannonRecoilOffset - cannonTr.Distance).Clamp(float.MinValue, -28.5f); LocalPosition = Vector3.Backward * (extents.x + cannonOffset); if (Input.Down(InputButton.Attack1) && timeSinceCannonFire > CannonCooldown) { cannonEnd = Position + player.Head.Rotation * cannonEndLocal; // Recalc cannon end after recoil and retracting was applied FireRocket(player, cannonEnd); } }
protected override void Update(GameTime gameTime) { #region Input. if (!IsActive) { return; } if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) { this.Exit(); } #endregion ///////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////// // Let's draw some stuff... // Draw code can be called from anywhere in the application - // even in separate threads! Use it for AI, Physics, // Gameplay, Graphics, etc. // Lines. for (int i = 0; i < 25; ++i) { DebugOverlay.Line(new Vector3(.5f * i, 5, 5), new Vector3(.5f * i, 5, -5), Color.Tomato); DebugOverlay.Line(new Vector3(.5f * i, 3, 5), new Vector3(.5f * i, 3, -5), Color.Violet); DebugOverlay.Line(new Vector3(.5f * i, 1, 5), new Vector3(.5f * i, 1, -5), Color.SeaGreen); } // Points. float radius = 4; for (int i = 0; i < 360; i += 2) { float rads = i * MathHelper.Pi / 180; DebugOverlay.Point(new Vector3( (float)Math.Cos(rads) * radius, -5, (float)Math.Sin(rads) * radius), Color.Black); } // Spheres. DebugOverlay.Sphere(new Vector3(-10, 0, 5), 3, Color.Brown); DebugOverlay.Sphere(new Vector3(-5, 0, 5), 2, Color.DarkGreen); DebugOverlay.Sphere(new Vector3(-2, 0, 5), 1, Color.Crimson); DebugOverlay.Sphere(new Vector3(-.5f, 0, 5), .5f, Color.DarkSalmon); // Bounding box. DebugOverlay.BoundingBox(new BoundingBox(new Vector3(-10, 0, -10), new Vector3(-5, 5, -5)), Color.RoyalBlue); // Screen text. DebugOverlay.ScreenText("FPS: " + mFps.FramesPerSecond, new Vector2(5, 7), Color.DimGray); DebugOverlay.ScreenText("FPS: " + mFps.FramesPerSecond, new Vector2(7, 5), Color.White); // Arrows. DebugOverlay.Arrow(Vector3.Zero, Vector3.UnitX * 10, 1, Color.Red); DebugOverlay.Arrow(Vector3.Zero, Vector3.UnitY * 10, 1, Color.Green); DebugOverlay.Arrow(Vector3.Zero, Vector3.UnitZ * 10, 1, Color.Blue); ///////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////// #region Update. mCamera.Update((float)gameTime.ElapsedGameTime.TotalSeconds); mFps.Tick(); base.Update(gameTime); #endregion }
public void OnFrame() { var pos = WorldPos; var right = WorldRot.Right * 4; var forward = WorldRot.Forward * 4; var up = WorldRot.Up * 50; var offset = Time.Now * 2.0f; var offsetz = Time.Now * 0.1f; var mode = (int)((Time.Now * 0.3f) % 5); switch (mode) { case 0: { DebugOverlay.Text(pos, "Perlin"); break; } case 1: { DebugOverlay.Text(pos, "SparseConvolution"); break; } case 2: { DebugOverlay.Text(pos, "SparseConvolutionNormalized"); break; } case 3: { DebugOverlay.Text(pos, "Turbulence"); break; } case 4: { DebugOverlay.Text(pos, "Fractal"); break; } } var size = 100; pos -= right * size * 0.5f; pos -= forward * size * 0.5f; for (float x = 0; x < size; x++) { for (float y = 0; y < size; y++) { float val = 0; switch (mode) { case 0: { val = Noise.Perlin(x * 0.1f + offset, y * 0.1f, offsetz) * 0.5f; break; } case 1: { val = Noise.SparseConvolution(x * 0.1f + offset, y * 0.1f, offsetz) * 0.5f; break; } case 2: { val = Noise.SparseConvolutionNormalized(x * 0.1f + offset, y * 0.1f, offsetz) * 0.5f; break; } case 3: { val = Noise.Turbulence(2, x * 0.1f + offset, y * 0.1f, offsetz) * 0.5f; break; } case 4: { val = Noise.Fractal(2, x * 0.1f + offset, y * 0.1f, offsetz) * 0.5f; break; } } var start = pos + x * right + y * forward; DebugOverlay.Line(start, start + up * val, Color.Lerp(Color.Red, Color.Green, (val + 1.0f) / 2.0f)); } } }
public override void OnPlayerControlTick(TankPlayer player, bool allowDebug) { float throttle = Input.Forward; float steering = Input.Left * (throttle >= 0 ? 1 : -1) * TurnSpeed; var moveDelta = Rotation.Forward * throttle * MoveSpeed; // Corner world position var frontLeft = moveDelta + Position + Rotation * (extents * new Vector3(1, 1, 0.5f)); var frontRight = moveDelta + Position + Rotation * (extents * new Vector3(1, -1, 0.5f)); var backLeft = moveDelta + Position + Rotation * (extents * new Vector3(-1, 1, 0.5f)); var backRight = moveDelta + Position + Rotation * extents * new Vector3(-1, -1, 0.5f); var center = moveDelta + Position + extents * new Vector3(0, 0, 0.5f); // Base world directions var frontDir = Rotation.Forward; var leftDir = Rotation.Left; var backDir = Rotation.Backward; var rightDir = Rotation.Right; var frontLeftDir = (frontDir + leftDir).Normal; var frontRightDir = (frontDir + rightDir).Normal; var backLeftDir = (backDir + leftDir).Normal; var backRightDir = (backDir + rightDir).Normal; float testDistance = 2 * MoveSpeed; if (allowDebug) { /*DebugOverlay.Axis(frontLeft, Rotation); * DebugOverlay.Axis(frontRight, Rotation); * DebugOverlay.Axis(backLeft, Rotation); * DebugOverlay.Axis(backRight, Rotation);*/ DebugOverlay.Sphere(frontLeft, testDistance, Color.Red); DebugOverlay.Sphere(frontRight, testDistance, Color.Red); DebugOverlay.Sphere(backLeft, testDistance, Color.Red); DebugOverlay.Sphere(backRight, testDistance, Color.Red); } var cornerResults = new TraceResult[12]; cornerResults[0] = Trace.Ray(frontLeft, frontLeft + frontDir * testDistance).Run(); // Forward cornerResults[1] = Trace.Ray(frontLeft, frontLeft + leftDir * testDistance).Run(); // Side cornerResults[2] = Trace.Ray(frontLeft, frontLeft + frontRightDir * testDistance).Run(); // Diagonal inward cornerResults[3] = Trace.Ray(frontRight, frontRight + frontDir * testDistance).Run(); cornerResults[4] = Trace.Ray(frontRight, frontRight + rightDir * testDistance).Run(); cornerResults[5] = Trace.Ray(frontRight, frontRight + frontLeftDir * testDistance).Run(); cornerResults[6] = Trace.Ray(backLeft, backLeft + backDir * testDistance).Run(); cornerResults[7] = Trace.Ray(backLeft, backLeft + leftDir * testDistance).Run(); cornerResults[8] = Trace.Ray(backLeft, backLeft + backRightDir * testDistance).Run(); cornerResults[9] = Trace.Ray(backRight, backRight + backDir * testDistance).Run(); cornerResults[10] = Trace.Ray(backRight, backRight + rightDir * testDistance).Run(); cornerResults[11] = Trace.Ray(backRight, backRight + backLeftDir * testDistance).Run(); // Turn off movement and don't collide if both front or back corners are colliding (but only if pushing against an axis-aligned wall) bool noMove = (cornerResults[0].Hit && cornerResults[3].Hit) || (cornerResults[6].Hit && cornerResults[9].Hit); float yaw = Rotation.Yaw(); noMove &= yaw.SnapToGrid(90).AlmostEqual(yaw, 2); if (noMove) { moveDelta = 0; } for (int i = 0; i < cornerResults.Length; i++) { var result = cornerResults[i]; if (!result.Hit) { if (allowDebug) { DebugOverlay.Line(result.StartPos, result.StartPos + result.Direction * testDistance * 8, Color.Green, 0, false); } continue; } if (i % 3 != 0 && cornerResults[i - (i % 3)].Hit) // Skip the side and diagonal tests if the primary one already hit { if (allowDebug) { DebugOverlay.Line(result.StartPos, result.StartPos + result.Direction * testDistance * 8, Color.Yellow, 0, false); } continue; } if (i % 3 == 0 && noMove) { continue; } if (allowDebug) { DebugOverlay.Line(result.StartPos, result.StartPos + result.Direction * testDistance * 8, Color.Red, 0, false); } Position += result.Normal * (testDistance - result.Distance); float torque = Vector3.Cross(result.StartPos - center, result.Normal).z *Time.Delta; Rotation *= Rotation.FromYaw(torque); if (allowDebug) { DebugOverlay.Line(result.EndPos, result.EndPos + result.Normal * MathF.Abs(torque) * 50, Color.Blue, 0, false); } } var edgeResults = new TraceResult[8]; edgeResults[0] = Trace.Ray(frontLeft, frontRight).Run(); edgeResults[2] = Trace.Ray(backLeft, backRight).Run(); edgeResults[4] = Trace.Ray(frontLeft, backLeft).Run(); edgeResults[6] = Trace.Ray(frontRight, backRight).Run(); for (int i = 0; i < edgeResults.Length; i += 2) { var result = edgeResults[i]; if (result.Hit) // Only run the partner trace if the first one on that edge hits { if (i == 0) { edgeResults[i + 1] = Trace.Ray(frontRight, frontLeft).Run(); } else if (i == 2) { edgeResults[i + 1] = Trace.Ray(backRight, backLeft).Run(); } else if (i == 4) { edgeResults[i + 1] = Trace.Ray(backLeft, frontLeft).Run(); } else if (i == 6) { edgeResults[i + 1] = Trace.Ray(backRight, frontRight).Run(); } } else { continue; } if (allowDebug) { DebugOverlay.Line(result.StartPos, result.EndPos, 0, false); } Vector3 correctionDir = Vector3.Zero; if (i == 0) { correctionDir = frontDir; } else if (i == 2) { correctionDir = backDir; } else if (i == 4) { correctionDir = leftDir; } else if (i == 6) { correctionDir = rightDir; } float distance = Vector3.DistanceBetween(result.EndPos, edgeResults[i + 1].EndPos); float angle = Vector3.GetAngle(result.EndPos - result.StartPos, result.Normal) - 90; float correction = distance * 0.5f * MathF.Sin(2 * angle.DegreeToRadian()); // How much to push out of the corner, finds the altitude of a right triangle Position -= correctionDir * correction; var avgPos = (result.EndPos + edgeResults[i + 1].EndPos) * 0.5f; float torque = Vector3.Cross(avgPos - center, -correctionDir).z *Time.Delta; Rotation *= Rotation.FromYaw(torque); if (allowDebug) { DebugOverlay.Line(avgPos, avgPos - correctionDir * MathF.Abs(torque) * 50, Color.Cyan, 0, false); DebugOverlay.Line(result.EndPos, edgeResults[i + 1].EndPos, Color.Red, 0, false); DebugOverlay.Line(result.EndPos, result.EndPos + correctionDir * correction * 10, Color.Green, 0, false); DebugOverlay.Line(edgeResults[i + 1].EndPos, edgeResults[i + 1].EndPos + correctionDir * correction * 10, Color.Green, 0, false); } } moveDelta = moveDelta.WithZ(0); Position += moveDelta; Rotation *= Rotation.FromYaw(steering); }
public void OnPrePhysicsStep() { if (!IsServer) { return; } var selfBody = PhysicsBody; if (!selfBody.IsValid()) { return; } var body = selfBody.SelfOrParent; if (!body.IsValid()) { return; } var dt = Time.Delta; body.DragEnabled = false; var rotation = selfBody.Rotation; accelerateDirection = currentInput.throttle.Clamp(-1, 1) * (1.0f - currentInput.breaking); TurnDirection = TurnDirection.LerpTo(currentInput.turning.Clamp(-1, 1), 1.0f - MathF.Pow(0.001f, dt)); airRoll = airRoll.LerpTo(currentInput.roll.Clamp(-1, 1), 1.0f - MathF.Pow(0.0001f, dt)); airTilt = airTilt.LerpTo(currentInput.tilt.Clamp(-1, 1), 1.0f - MathF.Pow(0.0001f, dt)); float targetTilt = 0; float targetLean = 0; var localVelocity = rotation.Inverse * body.Velocity; if (backWheelsOnGround || frontWheelsOnGround) { var forwardSpeed = MathF.Abs(localVelocity.x); var speedFraction = MathF.Min(forwardSpeed / 500.0f, 1); targetTilt = accelerateDirection.Clamp(-1.0f, 1.0f); targetLean = speedFraction * TurnDirection; } AccelerationTilt = AccelerationTilt.LerpTo(targetTilt, 1.0f - MathF.Pow(0.01f, dt)); TurnLean = TurnLean.LerpTo(targetLean, 1.0f - MathF.Pow(0.01f, dt)); if (backWheelsOnGround) { var forwardSpeed = MathF.Abs(localVelocity.x); var speedFactor = 1.0f - (forwardSpeed / 5000.0f).Clamp(0.0f, 1.0f); var acceleration = speedFactor * (accelerateDirection < 0.0f ? car_accelspeed * 0.5f : car_accelspeed) * accelerateDirection * dt; var impulse = rotation * new Vector3(acceleration, 0, 0); body.Velocity += impulse; } RaycastWheels(rotation, true, out frontWheelsOnGround, out backWheelsOnGround, dt); var onGround = frontWheelsOnGround || backWheelsOnGround; var fullyGrounded = (frontWheelsOnGround && backWheelsOnGround); Grounded = onGround; if (fullyGrounded) { body.Velocity += Map.Physics.Gravity * dt; } body.GravityScale = fullyGrounded ? 0 : 1; bool canAirControl = false; var v = rotation * localVelocity.WithZ(0); var vDelta = MathF.Pow((v.Length / 1000.0f).Clamp(0, 1), 5.0f).Clamp(0, 1); if (vDelta < 0.01f) { vDelta = 0; } if (debug_car) { DebugOverlay.Line(body.MassCenter, body.MassCenter + rotation.Forward.Normal * 100, Color.White, 0, false); DebugOverlay.Line(body.MassCenter, body.MassCenter + v.Normal * 100, Color.Green, 0, false); } var angle = (rotation.Forward.Normal * MathF.Sign(localVelocity.x)).Normal.Dot(v.Normal).Clamp(0.0f, 1.0f); angle = angle.LerpTo(1.0f, 1.0f - vDelta); grip = grip.LerpTo(angle, 1.0f - MathF.Pow(0.001f, dt)); if (debug_car) { DebugOverlay.ScreenText(new Vector2(200, 200), $"{grip}"); } var angularDamping = 0.0f; angularDamping = angularDamping.LerpTo(5.0f, grip); body.LinearDamping = 0.0f; body.AngularDamping = fullyGrounded ? angularDamping : 0.5f; if (onGround) { localVelocity = rotation.Inverse * body.Velocity; WheelSpeed = localVelocity.x; var turnAmount = frontWheelsOnGround ? (MathF.Sign(localVelocity.x) * 25.0f * CalculateTurnFactor(TurnDirection, MathF.Abs(localVelocity.x)) * dt) : 0.0f; body.AngularVelocity += rotation * new Vector3(0, 0, turnAmount); airRoll = 0; airTilt = 0; var forwardGrip = 0.1f; forwardGrip = forwardGrip.LerpTo(0.9f, currentInput.breaking); body.Velocity = VelocityDamping(Velocity, rotation, new Vector3(forwardGrip, grip, 0), dt); } else { var s = selfBody.Position + (rotation * selfBody.LocalMassCenter); var tr = Trace.Ray(s, s + rotation.Down * 50) .Ignore(this) .Run(); if (debug_car) { DebugOverlay.Line(tr.StartPosition, tr.EndPosition, tr.Hit ? Color.Red : Color.Green); } canAirControl = !tr.Hit; } if (canAirControl && (airRoll != 0 || airTilt != 0)) { var offset = 50 * Scale; var s = selfBody.Position + (rotation * selfBody.LocalMassCenter) + (rotation.Right * airRoll * offset) + (rotation.Down * (10 * Scale)); var tr = Trace.Ray(s, s + rotation.Up * (25 * Scale)) .Ignore(this) .Run(); if (debug_car) { DebugOverlay.Line(tr.StartPosition, tr.EndPosition); } bool dampen = false; if (currentInput.roll.Clamp(-1, 1) != 0) { var force = tr.Hit ? 400.0f : 100.0f; var roll = tr.Hit ? currentInput.roll.Clamp(-1, 1) : airRoll; body.ApplyForceAt(selfBody.MassCenter + rotation.Left * (offset * roll), (rotation.Down * roll) * (roll * (body.Mass * force))); if (debug_car) { DebugOverlay.Sphere(selfBody.MassCenter + rotation.Left * (offset * roll), 8, Color.Red); } dampen = true; } if (!tr.Hit && currentInput.tilt.Clamp(-1, 1) != 0) { var force = 200.0f; body.ApplyForceAt(selfBody.MassCenter + rotation.Forward * (offset * airTilt), (rotation.Down * airTilt) * (airTilt * (body.Mass * force))); if (debug_car) { DebugOverlay.Sphere(selfBody.MassCenter + rotation.Forward * (offset * airTilt), 8, Color.Green); } dampen = true; } if (dampen) { body.AngularVelocity = VelocityDamping(body.AngularVelocity, rotation, 0.95f, dt); } } localVelocity = rotation.Inverse * body.Velocity; MovementSpeed = localVelocity.x; }
/// <summary> /// We adjust the ball's linear / angular damping based on the surface. /// This can be done clientside for prediction. /// </summary> protected void AdjustDamping() { var downTrace = Trace.Ray(Position, Position + Vector3.Down * OOBBox.Size.z); downTrace.HitLayer(CollisionLayer.Solid); downTrace.Ignore(this); var downTraceResult = downTrace.Run(); if (Debug) { DebugOverlay.Line(downTraceResult.StartPos, downTraceResult.EndPos); // if ( downTraceResult.Entity.IsValid() ) // DebugOverlay.Text( downTraceResult.StartPos, $"e: {downTraceResult.Entity.EngineEntityName}" ); } // We are in the air, do nothing? (Maybe we could adjust something to make ball airtime feel nicer?) if (!downTraceResult.Hit) { return; } // See if we're on a flat surface by checking the dot product of the surface normal. if (downTraceResult.Normal.Dot(Vector3.Up).AlmostEqual(1, 0.001f)) { switch (downTraceResult.Surface.Name) { case "minigolf.sand": PhysicsBody.LinearDamping = 2.5f; PhysicsBody.AngularDamping = 2.5f; break; case "minigolf.ice": PhysicsBody.LinearDamping = 0.25f; PhysicsBody.AngularDamping = 0.00f; break; default: PhysicsBody.LinearDamping = DefaultLinearDamping; PhysicsBody.AngularDamping = DefaultAngularDamping; break; } if (downTraceResult.Entity is SpeedBoost speedBoost) { // TODO: Multiply by delta time var velocity = PhysicsBody.Velocity; velocity += Angles.AngleVector(speedBoost.MoveDir) * speedBoost.SpeedMultiplier; PhysicsBody.Velocity = velocity; } return; } // We must be on a hill, we can detect if it's up hill or down hill by doing a forward trace var trace = Trace.Ray(Position, Position + PhysicsBody.Velocity.WithZ(0)); trace.HitLayer(CollisionLayer.Debris); trace.Ignore(this); var traceResult = trace.Run(); if (traceResult.Hit) { PhysicsBody.LinearDamping = 0.015f; PhysicsBody.AngularDamping = 2.00f; return; } PhysicsBody.LinearDamping = 0.0f; PhysicsBody.AngularDamping = 1.0f; }