public GameVelocity TryGetLaunchVelocity(CameraInfo cameraInfo) { GameVelocity result = null; if (this.Projectile == null) { throw new Exception("Trying to launch without a ball"); } // Move the catapult to make sure that it is moved at least once before launch (prevent NaN in launch direction) this.Move(cameraInfo); var stretchNormalized = DigitExtensions.Clamp((this.stretch - this.properties.MinStretch) / (this.properties.MaxStretch - this.properties.MinStretch), 0.0, 1.0); // this is a lerp var velocity = (float)(this.properties.MinVelocity * (1d - stretchNormalized) + this.properties.MaxVelocity * stretchNormalized); var launchDir = SCNVector3.Normalize(this.pullOrigin.WorldPosition - this.Projectile.WorldPosition); if (!launchDir.HasNaN()) { var liftFactor = 0.05f * Math.Abs(1f - SCNVector3.Dot(launchDir, SCNVector3.UnitY)); // used to keep ball in air longer var lift = SCNVector3.UnitY * velocity * liftFactor; result = new GameVelocity(this.Projectile.WorldPosition, launchDir * velocity + lift); } return(result); }
public override void Update(double deltaTimeInSeconds) { this.currentTime += deltaTimeInSeconds; this.CalcCurrentFrameIndex(); var alpha = (float)((this.currentTime - this.wayPoints[currentFrame].Time) / (this.wayPoints[currentFrame + 1].Time - this.wayPoints[currentFrame].Time)); alpha = DigitExtensions.Clamp(alpha, 0f, 1f); var curPos = this.wayPoints[currentFrame].Pos; var curTan = this.wayPoints[currentFrame].Tangent; var curRot = this.wayPoints[currentFrame].Rot; var nextPos = this.wayPoints[currentFrame + 1].Pos; var nextTan = this.wayPoints[currentFrame + 1].Tangent; var nextRot = this.wayPoints[currentFrame + 1].Rot; var newPosX = this.HermiteCurve(curPos.X, nextPos.X, curTan.X, nextTan.X, alpha); var newPosY = this.HermiteCurve(curPos.Y, nextPos.Y, curTan.Y, nextTan.Y, alpha); var newPosZ = this.HermiteCurve(curPos.Z, nextPos.Z, curTan.Z, nextTan.Z, alpha); var newQuat = SCNQuaternion.Slerp(curRot, nextRot, alpha); node.WorldPosition = new SCNVector3(newPosX, newPosY, newPosZ); node.WorldOrientation = newQuat; // update child rigid bodies to percolate into physics if (this.Entity is GameObject entity) { if (entity.PhysicsNode?.PhysicsBody != null) { entity.PhysicsNode.PhysicsBody.ResetTransform(); } } }
public void Update(CameraInfo cameraInfo) { if (this.DisplayedVictory) { // Enlarge victory text before falling down this.victoryNode.Opacity = (float)(DigitExtensions.Clamp((GameTime.Time - this.activationStartTime) / FadeTime, 0d, 1d)); if (GameTime.Time - this.activationStartTime > TimeUntilPhysicsReleased) { foreach (var child in this.victoryNode.ChildNodes) { if (child.PhysicsBody != null) { child.PhysicsBody.VelocityFactor = SCNVector3.One; child.PhysicsBody.AngularVelocityFactor = SCNVector3.One; } } } } else { // Update win condition if (this.DidWin() && this.teamWon != Team.None) { this.ActivateVictory(); } } }
/// <summary> /// Incrementally scales the board by the given amount /// </summary> public void UpdateScale(float factor) { // assumes we always scale the same in all 3 dimensions var currentScale = this.Scale.X; var newScale = DigitExtensions.Clamp(currentScale * factor, GameBoard.MinimumScale, GameBoard.MaximumScale); this.Scale = new SCNVector3(newScale, newScale, newScale); }
private void UpdateMusicVolume() { var volume = UserDefaults.MusicVolume; // Map the slider value from 0...1 to a more natural curve: this.musicGain = volume * volume; foreach (var(_, player) in this.musicPlayers.Where(player => player.Value.State == MusicState.Playing)) { var audioVolume = DigitExtensions.Clamp(this.musicGain * (float)Math.Pow(10f, player.Config.VolumeDB / 20f), 0f, 1f); player.AudioPlayer.SetVolume(audioVolume, 0.1d); } }
public void PlayLaunch(GameVelocity velocity) { // For the launch, we will play two sounds: a launch twang and a swish var length = velocity.Vector.Length; length = float.IsNaN(length) ? 0 : DigitExtensions.Clamp(length, 0, 1); var launchVel = (byte)(length * 30 + 80); this.Play(Note.Launch, launchVel); var swishVel = (byte)(length * 63 + 64); this.After(() => this.Play(Note.LaunchSwish, swishVel), 1); }
public MusicPlayer PlayMusic(string name, double startTime, double fadeIn = 0d) { var player = this.MusicPlayer(name); var audioPlayer = player.AudioPlayer; if (this.CurrentMusicPlayer != null) { this.StopMusic(this.CurrentMusicPlayer); } switch (player.State) { case MusicState.Playing: // Nothing to do return(player); case MusicState.Stopped: // Configure the audioPlayer, starting with volume at 0 and then fade in. audioPlayer.Volume = 0; audioPlayer.CurrentTime = 0; if (player.Config.Loops) { audioPlayer.NumberOfLoops = -1; } else { audioPlayer.NumberOfLoops = 0; } audioPlayer.CurrentTime = startTime; audioPlayer.Play(); break; case MusicState.Stopping: // Leave it playing. Update the volume and play state below. break; } var volume = DigitExtensions.Clamp(this.musicGain * (float)Math.Pow(10f, player.Config.VolumeDB / 20f), 0f, 1f); audioPlayer.SetVolume(volume, fadeIn); player.State = MusicState.Playing; this.CurrentMusicPlayer = player; return(player); }
public AnimWaypointComponent(SCNNode node, Dictionary <string, object> properties) : base() { this.node = node; if (properties.TryGetValue("speed", out object speed)) { this.speed = (double)speed; } // find all waypoints this.InitWaypoints(this.node); this.CalculateTangents(); // does this animation support random start times? if (properties.TryGetValue("random", out object random)) { if (random is bool isRandom && isRandom && this.wayPoints.Any()) { var last = this.wayPoints.Last(); this.currentTime = new Random().NextDouble() * last.Time; } } // do we want to start at a particular percentage along curve? if (properties.TryGetValue("phase", out object objectPhase)) { if (objectPhase is double phase && this.wayPoints.Any()) { var desiredPhase = DigitExtensions.Clamp(phase, 0d, 1d); var last = this.wayPoints.Last(); this.currentTime = desiredPhase * last.Time; } } // do we want to start at a specific point in time? if (properties.TryGetValue("offset", out object objectOffset)) { if (objectOffset is double offset && this.wayPoints.Any()) { var last = this.wayPoints.Last(); this.currentTime = offset * last.Time; } } }
public void SimulateStep(double time) { var minUpdateSeconds = 1f / 120f; var maxUpdateSeconds = 1f / 30f; var seconds = DigitExtensions.Clamp((float)(time - this.time), minUpdateSeconds, maxUpdateSeconds); // could run multiple iterations if greater than maxUpdateSeconds, but for now just run one this.ApplyForces(); this.AverageVelocities(); // copy the current state var currentTransforms = new List <SimulatedTransform>(); currentTransforms.AddRange(this.simulatedTransforms); // simulate forward for (var i = this.BoneInset; i < this.simulatedTransforms.Count - this.BoneInset; i++) { if (!currentTransforms[i].Dynamic) { continue; } var p = currentTransforms[i].Position; var v = currentTransforms[i].Velocity; p += v * seconds; this.simulatedTransforms[i].Position = p; } this.PerformPlaneCollision(currentTransforms, seconds); this.AlignBones(); this.time = time; }
private SCNVector3 ComputeBallPosition(CameraInfo cameraInfo) { var cameraRay = cameraInfo.Ray; // These should be based on the projectile radius. // This affects centering of ball, and can hit near plane of camera // This is always centering to one edge of screen independent of screen orient // We always want the ball at the bottom of the screen. var distancePullToCamera = 0.21f; var ballShiftDown = 0.2f; var targetBallPosition = cameraRay.Position + cameraRay.Direction * distancePullToCamera; var cameraDown = -SCNVector4.Normalize(cameraInfo.Transform.Column1).Xyz; targetBallPosition += cameraDown * ballShiftDown; // Clamp to only the valid side var pullWorldPosition = this.pullOrigin.WorldPosition; if (pullWorldPosition.Z < 0f) { targetBallPosition.Z = Math.Min(targetBallPosition.Z, pullWorldPosition.Z); } else { targetBallPosition.Z = Math.Max(targetBallPosition.Z, pullWorldPosition.Z); } // Clamp to cone/circular core var yDistanceFromPull = Math.Max(0f, pullWorldPosition.Y - targetBallPosition.Y); var minBallDistanceFromPull = 0.5f; var pullBlockConeSlope = 1.0f; var pullBlockConeRadius = yDistanceFromPull / pullBlockConeSlope; var pullBlockCoreRadius = Math.Max(minBallDistanceFromPull, pullBlockConeRadius); // if pull is in the core, move it out. var pullWorldPositionGrounded = new SCNVector3(pullWorldPosition.X, 0f, pullWorldPosition.Z); var targetPullPositionGrounded = new SCNVector3(targetBallPosition.X, 0f, targetBallPosition.Z); var targetInitialToTargetPull = targetPullPositionGrounded - pullWorldPositionGrounded; if (pullBlockCoreRadius > targetInitialToTargetPull.Length) { var moveOutDirection = SCNVector3.Normalize(targetInitialToTargetPull); var newTargetPullPositionGrounded = pullWorldPositionGrounded + moveOutDirection * pullBlockCoreRadius; targetBallPosition = new SCNVector3(newTargetPullPositionGrounded.X, targetBallPosition.Y, newTargetPullPositionGrounded.Z); } // only use the 2d distance, so that user can gauage stretch indepdent of mtch var distance2D = targetBallPosition - pullWorldPosition; var stretchY = Math.Abs(distance2D.Y); distance2D.Y = 0f; var stretchDistance = distance2D.Length; this.stretch = DigitExtensions.Clamp((double)stretchDistance, this.properties.MinStretch, this.properties.MaxStretch); // clamp a little bit farther than maxStretch // can't let the strap move back too far right now var clampedStretchDistance = (float)(1.1d * this.properties.MaxStretch); if (stretchDistance > clampedStretchDistance) { targetBallPosition = (clampedStretchDistance / stretchDistance) * (targetBallPosition - pullWorldPosition) + pullWorldPosition; stretchDistance = clampedStretchDistance; } // Make this optional, not required. You're often at max stretch. // Also have a timer for auto-launch. This makes it very difficuilt to test // storing state in member data this.IsPulledTooFar = stretchDistance > (float)(this.properties.MaxStretch) || stretchY > (float)(this.properties.MaxStretch); return(targetBallPosition); }
public void DoHighlight(bool show, SFXCoordinator sfxCoordinator) { if (this.HighlightObject != null) { this.IsHighlighted = show; this.HighlightObject.Hidden = !show; if (show) { var seconds = (DateTime.UtcNow - Time1970).TotalSeconds; var intensity = (float)(Math.Sin(seconds.TruncatingRemainder(1) * 3.1415 * 2.0) * 0.2); if (this.HighlightObject.Geometry?.FirstMaterial != null) { var color = new CIColor(this.highlightColor); this.HighlightObject.Geometry.FirstMaterial.Diffuse.Contents = UIColor.FromRGBA((Single)DigitExtensions.Clamp(color.Red + intensity, 0, 1), (Single)DigitExtensions.Clamp(color.Green + intensity, 0, 1), (Single)DigitExtensions.Clamp(color.Blue + intensity, 0, 1), (Single)1); } } sfxCoordinator?.CatapultDidChangeHighlight(this, show); } }
public void PlayStretch(Catapult catapult, float stretchDistance, float stretchRate, bool playHaptic) { var normalizedDistance = DigitExtensions.Clamp((stretchDistance - 0.1f) / 2f, 0f, 1f); if (this.isStretchPlaying) { // Set the stretch distance and rate on the audio // player to module the strech sound effect. catapult.AudioPlayer.StretchDistance = normalizedDistance; catapult.AudioPlayer.StretchRate = stretchRate; } else { catapult.AudioPlayer.StartStretch(); this.isStretchPlaying = true; } if (playHaptic) { double interval; if (normalizedDistance >= 0.25 && normalizedDistance < 0.5) { interval = 0.5; } else if (normalizedDistance >= 0.5) { interval = 0.25; } else { interval = 1.0; } if (this.prevStretchDistance.HasValue) { var delta = Math.Abs(stretchDistance - this.prevStretchDistance.Value); this.prevStretchDistance = stretchDistance; if (delta < 0.0075f) { return; } } else { this.prevStretchDistance = stretchDistance; } if (this.timeSinceLastHaptic.HasValue) { if ((DateTime.UtcNow - this.timeSinceLastHaptic.Value).TotalSeconds > interval) { this.hapticsGenerator.GenerateImpactFeedback(); this.timeSinceLastHaptic = DateTime.UtcNow; } } else { this.hapticsGenerator.GenerateImpactFeedback(); this.timeSinceLastHaptic = DateTime.UtcNow; } } }
public CollisionEvent CreateCollisionEvent(float impulse, bool withBall, bool withTable) { CollisionEvent result = null; if (float.IsNaN(impulse) || impulse < this.configuration.MinimumImpulse) { } else { // Set mod wheel according to the impulse value. This will vary the attack of the sound // and make them less repetitive and more dynamic. The sampler patch is set up to play the full // sound with modWheel off, and shortened attack with increasing modwheel value. So we invert the // normalized range. // // Also, we want to alter the velocity so that higher impulse means a louder note. byte note; if (withTable) { note = Note.CollisionWithTable; } else if (withBall) { note = Note.CollisionWithBall; } else { note = Note.CollisionWithBlock; } note = (byte)(note + this.variant[0]); // move this variant randomly to another position var otherIndex = new Random().Next(variant.Length - 1);// Int(arc4random_uniform(UInt32(variant.count - 1))) //variant.swapAt(0, 1 + otherIndex) var temp = variant[0]; variant[0] = variant[1 + otherIndex]; variant[1 + otherIndex] = temp; var normalizedImpulse = DigitExtensions.Clamp((impulse - this.configuration.MinimumImpulse) / (this.configuration.MaximumImpulse - this.configuration.MinimumImpulse), 0f, 1f); // Once the impulse is normalized to the range 0...1, doing a sqrt // on it causes lower values to be higher. This curve was chosen because // it sounded better aesthetically. normalizedImpulse = (float)Math.Sqrt(normalizedImpulse); var rangedImpulse = this.configuration.VelocityMinimum + (this.configuration.VelocityMaximum - this.configuration.VelocityMinimum) * normalizedImpulse; var velocity = (byte)(DigitExtensions.Clamp(rangedImpulse, 0, 127)); result = new CollisionEvent { Note = note, Velocity = velocity, ModWheel = 1f - normalizedImpulse, }; } return(result); }
private float StageProgress(double startTime, double endTime) { var progress = (float)((this.TimeSinceInitialFloatStart - startTime) / (endTime - startTime)); return(DigitExtensions.Clamp(progress, 0f, 1f)); }
public void AnimateVortex() { if (this.Delegate == null) { throw new Exception("No delegate"); } if (this.vortexCylinder == null) { throw new Exception("Vortex animation cylinder not set"); } // Vortex shape from animation var vortexShape = this.vortexCylinder.PresentationNode.Scale; var vortexHeightDelta = vortexShape.Y - this.lastVortexHeight; this.lastVortexHeight = vortexShape.Y; var vortexCenterY = this.vortexCylinder.PresentationNode.WorldPosition.Y; var vortexCenterYDelta = vortexCenterY - this.lastVortexCenterY; this.lastVortexCenterY = vortexCenterY; // Deform shape over time var maxOuterRadius = vortexShape.X; var maxInnerRadius = maxOuterRadius * 0.2f; // 20 % from experiment var maxOuterRadiusDelta = maxOuterRadius - this.lastOuterRadius; this.lastOuterRadius = maxOuterRadius; // Orbital velocity var currentFront = this.vortexCylinder.PresentationNode.WorldFront; var orbitalMoveDelta = (currentFront - this.lastFront).Length * maxInnerRadius; this.lastFront = currentFront; var orbitalVelocityFactor = 5f; var orbitalVelocity = (orbitalMoveDelta / (float)GameTime.DeltaTime) * orbitalVelocityFactor; var topBound = vortexCenterY + vortexShape.Y * 0.5f; var bottomBound = vortexCenterY - vortexShape.Y * 0.5f; var blockObjects = this.Delegate.AllBlockObjects; var up = SCNVector3.UnitY; foreach (var block in blockObjects) { if (block.PhysicsNode?.PhysicsBody != null) { var position = block.PhysicsNode.PresentationNode.WorldPosition; var positionWithoutY = new SCNVector3(position.X, 0f, position.Z); var distanceFromCenter = positionWithoutY.Length; var directionToCenter = -SCNVector3.Normalize(positionWithoutY); // Adjust radius into curve // Equation representing a half radius chord of circle equation var normalizedY = DigitExtensions.Clamp(position.Y / topBound, 0f, 1f); var radiusFactor = (float)Math.Sqrt(4f - 3f * normalizedY * normalizedY) - 1f; radiusFactor = radiusFactor * 0.8f + 0.2f; var innerRadius = maxInnerRadius * radiusFactor; var outerRadius = maxOuterRadius * radiusFactor; // Cap velocity var maxVelocity = 30f; if (block.PhysicsNode.PhysicsBody.Velocity.Length > maxVelocity) { block.PhysicsNode.PhysicsBody.Velocity = SCNVector3.Normalize(block.PhysicsNode.PhysicsBody.Velocity) * maxVelocity; } var force = SCNVector3.Zero; // Stage specific manipulation var vortexDirection = SCNVector3.Cross(directionToCenter, up); var speedInVortexDirection = SCNVector3.Dot(block.PhysicsNode.PhysicsBody.Velocity, vortexDirection); // Stable vortex pull var pullForceMagnitude = (speedInVortexDirection * speedInVortexDirection) * (float)(block.PhysicsNode.PhysicsBody.Mass) / distanceFromCenter; force += pullForceMagnitude * directionToCenter; // Pull into outer radius var radialInwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, distanceFromCenter - outerRadius); force += radialInwardForceMagnitude * directionToCenter; // Pull away from inner radius var radialOutwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, innerRadius - distanceFromCenter); force += -radialOutwardForceMagnitude * directionToCenter; // Vortex velocity adjustment if (distanceFromCenter > innerRadius) { var tangentForceMagnitude = TangentVelocitySpringContant * (speedInVortexDirection - orbitalVelocity); force += -tangentForceMagnitude * vortexDirection * (0.5f + (float)(random.NextDouble() * 1d)); } // Random forces/torque force += force.Length * (float)((random.NextDouble() * 2d - 1d) * MaxRandomVortexForce) * up; this.ApplyRandomTorque(block.PhysicsNode.PhysicsBody, MaxRandomVortexTorque); // Top bound pull down var topBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, position.Y - topBound); force += topBoundForceMagnitude * -up; // Bottom bound pull up var bottomBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, bottomBound - position.Y); force += bottomBoundForceMagnitude * up; block.PhysicsNode.PhysicsBody.ApplyForce(force, false); // Scale the vortex // The higher position in the bound, more it should move upward to scale the vortex var normalizedPositionInBoundY = DigitExtensions.Clamp((position.Y - bottomBound) / vortexShape.Y, 0f, 1f); var heightMoveFactor = Math.Abs(normalizedPositionInBoundY - 0.5f); var newPositionY = position.Y + vortexCenterYDelta + vortexHeightDelta * heightMoveFactor; var positionXZ = new SCNVector3(position.X, 0f, position.Z); var radialMoveFactor = DigitExtensions.Clamp(distanceFromCenter / outerRadius, 0f, 1f); var newPositionXZ = positionXZ + maxOuterRadiusDelta * radialMoveFactor * -directionToCenter; block.PhysicsNode.WorldPosition = new SCNVector3(newPositionXZ.X, newPositionY, newPositionXZ.Z); block.PhysicsNode.WorldOrientation = block.PhysicsNode.PresentationNode.WorldOrientation; block.PhysicsNode.PhysicsBody.ResetTransform(); } } }
public void Update(CameraInfo cameraInfo) { if (this.Delegate == null) { throw new Exception("No delegate"); } // Do not move the lever after it has been activated if (!(this.InteractionToActivate?.IsActivated ?? false)) { if (this.activeSwitch != null) { // Lever Pulling var cameraOffset = this.activeSwitch.PullOffset(cameraInfo.Ray.Position - this.startLeverHoldCameraPosition); var cameraMovedZ = cameraOffset.Z; var targetEulerX = this.startLeverEulerX + LeverPullZtoLeverEulerRotation * cameraMovedZ; targetEulerX = DigitExtensions.Clamp(-LeverMaxEulerX, targetEulerX, LeverMaxEulerX); this.activeSwitch.Angle = targetEulerX; if (targetEulerX <= -LeverMaxEulerX) { // Interaction activation once the switch lever is turned all the way this.InteractionToActivate?.Activate(); // Fade out the switches var waitAction = SCNAction.Wait(3f); var fadeAction = SCNAction.FadeOut(3d); foreach (var resetSwitch in this.resetSwitches) { resetSwitch.Base.RunAction(SCNAction.Sequence(new SCNAction[] { waitAction, fadeAction })); } return; } else { // Inform peers of the movement var leverId = this.resetSwitches.IndexOf(this.activeSwitch); if (leverId == -1) { throw new Exception("No lever in array"); } this.Delegate.DispatchActionToServer(new GameActionType { LeverMove = new LeverMove(leverId, targetEulerX), Type = GameActionType.GActionType.LeverMove }); } } else { // Lever spring back foreach (var lever in this.resetSwitches.Where(lever => lever.Angle < LeverMaxEulerX)) { lever.Angle = Math.Min(LeverMaxEulerX, lever.Angle + LeverSpringBackSpeed * (float)GameTime.DeltaTime); } } // Highlight lever when nearby, otherwise check if we should hide the highlight if (this.highlightedSwitch != null) { if (!this.highlightedSwitch.ShouldHighlight(cameraInfo.Ray)) { this.highlightedSwitch.DoHighlight(false, this.SfxCoordinator); this.highlightedSwitch = null; } } else { foreach (var resetSwitch in this.resetSwitches) { if (resetSwitch.ShouldHighlight(cameraInfo.Ray)) { resetSwitch.DoHighlight(true, this.SfxCoordinator); this.highlightedSwitch = resetSwitch; } } } } }