public void ExperiencePressureDifference(int cycle, float pressureDifference, Direction direction, float pressureResistanceProbDelta, GridCoordinates throwTarget) { if (ControlledComponent == null) { return; } // TODO ATMOS stuns? var transform = ControlledComponent.Owner.Transform; var pressureComponent = ControlledComponent.Owner.GetComponent <MovedByPressureComponent>(); var maxForce = MathF.Sqrt(pressureDifference) * 2.25f; var moveProb = 100f; if (pressureComponent.PressureResistance > 0) { moveProb = MathF.Abs((pressureDifference / pressureComponent.PressureResistance * ProbabilityBasePercent) - ProbabilityOffset); } if (moveProb > ProbabilityOffset && _robustRandom.Prob(MathF.Min(moveProb / 100f, 1f)) && !float.IsPositiveInfinity(pressureComponent.MoveResist) && (!ControlledComponent.Anchored && (maxForce >= (pressureComponent.MoveResist * MoveForcePushRatio))) || (ControlledComponent.Anchored && (maxForce >= (pressureComponent.MoveResist * MoveForceForcePushRatio)))) { if (maxForce > ThrowForce && throwTarget != GridCoordinates.InvalidGrid) { var moveForce = MathF.Min(maxForce * MathF.Clamp(moveProb, 0, 100) / 100f, 50f); var pos = throwTarget.Position - transform.GridPosition.Position; LinearVelocity = pos * moveForce; } else { var moveForce = MathF.Min(maxForce * MathF.Clamp(moveProb, 0, 100) / 100f, 25f); LinearVelocity = direction.ToVec() * moveForce; } pressureComponent.LastHighPressureMovementAirCycle = cycle; } }
//[MethodImpl(MethodImplOptions.AggressiveInlining)] public void EqualizePressureInZone(int cycleNum) { if (Air == null || (_tileAtmosInfo.LastCycle >= cycleNum)) { return; // Already done. } _tileAtmosInfo = new TileAtmosInfo(); var startingMoles = Air.TotalMoles; var runAtmos = false; // We need to figure if this is necessary foreach (var(direction, other) in _adjacentTiles) { if (other?.Air == null) { continue; } var comparisonMoles = other.Air.TotalMoles; if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) { continue; } runAtmos = true; break; } if (!runAtmos) // There's no need so we don't bother. { _tileAtmosInfo.LastCycle = cycleNum; return; } var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; var totalMoles = 0f; var tiles = new TileAtmosphere[Atmospherics.ZumosHardTileLimit]; tiles[0] = this; _tileAtmosInfo.LastQueueCycle = queueCycle; var tileCount = 1; for (var i = 0; i < tileCount; i++) { if (i > Atmospherics.ZumosHardTileLimit) { break; } var exploring = tiles[i]; if (i < Atmospherics.ZumosTileLimit) { var tileMoles = exploring.Air.TotalMoles; exploring._tileAtmosInfo.MoleDelta = tileMoles; totalMoles += tileMoles; } foreach (var(_, adj) in exploring._adjacentTiles) { if (adj?.Air == null) { continue; } if (adj._tileAtmosInfo.LastQueueCycle == queueCycle) { continue; } adj._tileAtmosInfo = new TileAtmosInfo(); adj._tileAtmosInfo.LastQueueCycle = queueCycle; if (tileCount < Atmospherics.ZumosHardTileLimit) { tiles[tileCount++] = adj; } if (adj.Air.Immutable) { // Looks like someone opened an airlock to space! ExplosivelyDepressurize(cycleNum); return; } } } if (tileCount > Atmospherics.ZumosTileLimit) { for (var i = Atmospherics.ZumosTileLimit; i < tileCount; i++) { //We unmark them. We shouldn't be pushing/pulling gases to/from them. var tile = tiles[i]; if (tile == null) { continue; } tiles[i]._tileAtmosInfo.LastQueueCycle = 0; } tileCount = Atmospherics.ZumosTileLimit; } //tiles = tiles.AsSpan().Slice(0, tileCount).ToArray(); // According to my benchmarks, this is much slower. Array.Resize(ref tiles, tileCount); var averageMoles = totalMoles / (tiles.Length); var giverTiles = new List <TileAtmosphere>(); var takerTiles = new List <TileAtmosphere>(); for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; tile._tileAtmosInfo.LastCycle = cycleNum; tile._tileAtmosInfo.MoleDelta -= averageMoles; if (tile._tileAtmosInfo.MoleDelta > 0) { giverTiles.Add(tile); } else { takerTiles.Add(tile); } } var logN = MathF.Log2(tiles.Length); // Optimization - try to spread gases using an O(nlogn) algorithm that has a chance of not working first to avoid O(n^2) if (giverTiles.Count > logN && takerTiles.Count > logN) { // Even if it fails, it will speed up the next part. Array.Sort(tiles, (a, b) => a._tileAtmosInfo.MoleDelta.CompareTo(b._tileAtmosInfo.MoleDelta)); foreach (var tile in tiles) { tile._tileAtmosInfo.FastDone = true; if (!(tile._tileAtmosInfo.MoleDelta > 0)) { continue; } Direction eligibleAdjBits = 0; var amtEligibleAdj = 0; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } // skip anything that isn't part of our current processing block. Original one didn't do this unfortunately, which probably cause some massive lag. if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } eligibleAdjBits |= direction; amtEligibleAdj++; } if (amtEligibleAdj <= 0) { continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this. } var molesToMove = tile._tileAtmosInfo.MoleDelta / amtEligibleAdj; foreach (var direction in Cardinal) { if ((eligibleAdjBits & direction) == 0 || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } tile.AdjustEqMovement(direction, molesToMove); tile._tileAtmosInfo.MoleDelta -= molesToMove; tile2._tileAtmosInfo.MoleDelta += molesToMove; } } giverTiles.Clear(); takerTiles.Clear(); foreach (var tile in tiles) { if (tile._tileAtmosInfo.MoleDelta > 0) { giverTiles.Add(tile); } else { takerTiles.Add(tile); } } // This is the part that can become O(n^2). if (giverTiles.Count < takerTiles.Count) { // as an optimization, we choose one of two methods based on which list is smaller. We really want to avoid O(n^2) if we can. var queue = new List <TileAtmosphere>(takerTiles.Count); foreach (var giver in giverTiles) { giver._tileAtmosInfo.CurrentTransferDirection = (Direction)(-1); giver._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; queue.Clear(); queue.Add(giver); giver._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; var queueCount = queue.Count; for (var i = 0; i < queueCount; i++) { if (giver._tileAtmosInfo.MoleDelta <= 0) { break; // We're done here now. Let's not do more work than needed. } var tile = queue[i]; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } if (giver._tileAtmosInfo.MoleDelta <= 0) { break; // We're done here now. Let's not do more work than needed. } if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) { continue; } queue.Add(tile2); queueCount++; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2._tileAtmosInfo.CurrentTransferAmount = 0; if (tile2._tileAtmosInfo.MoleDelta < 0) { // This tile needs gas. Let's give it to 'em. if (-tile2._tileAtmosInfo.MoleDelta > giver._tileAtmosInfo.MoleDelta) { // We don't have enough gas! tile2._tileAtmosInfo.CurrentTransferAmount -= giver._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta += giver._tileAtmosInfo.MoleDelta; giver._tileAtmosInfo.MoleDelta = 0; } else { // We have enough gas. tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta; giver._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta = 0; } } } } // Putting this loop here helps make it O(n^2) over O(n^3) for (var i = queue.Count - 1; i >= 0; i--) { var tile = queue[i]; if (tile._tileAtmosInfo.CurrentTransferAmount != 0 && tile._tileAtmosInfo.CurrentTransferDirection != (Direction)(-1)) { tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) { adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; } tile._tileAtmosInfo.CurrentTransferAmount = 0; } } } } else { var queue = new List <TileAtmosphere>(giverTiles.Count); foreach (var taker in takerTiles) { taker._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; taker._tileAtmosInfo.CurrentTransferAmount = 0; var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; queue.Clear(); queue.Add(taker); taker._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; var queueCount = queue.Count; for (int i = 0; i < queueCount; i++) { if (taker._tileAtmosInfo.MoleDelta >= 0) { break; // We're done here now. Let's not do more work than needed. } var tile = queue[i]; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.ContainsKey(direction)) { continue; } var tile2 = tile._adjacentTiles[direction]; if (taker._tileAtmosInfo.MoleDelta >= 0) { break; // We're done here now. Let's not do more work than needed. } if (tile2?._tileAtmosInfo == null || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) { continue; } if (tile2._tileAtmosInfo.LastSlowQueueCycle == queueCycleSlow) { continue; } queue.Add(tile2); queueCount++; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile2._tileAtmosInfo.CurrentTransferDirection = direction.GetOpposite(); tile2._tileAtmosInfo.CurrentTransferAmount = 0; if (tile2._tileAtmosInfo.MoleDelta > 0) { // This tile has gas we can suck, so let's if (tile2._tileAtmosInfo.MoleDelta > -taker._tileAtmosInfo.MoleDelta) { // They have enough gas tile2._tileAtmosInfo.CurrentTransferAmount -= taker._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta += taker._tileAtmosInfo.MoleDelta; taker._tileAtmosInfo.MoleDelta = 0; } else { // They don't have enough gas! tile2._tileAtmosInfo.CurrentTransferAmount += tile2._tileAtmosInfo.MoleDelta; taker._tileAtmosInfo.MoleDelta += tile2._tileAtmosInfo.MoleDelta; tile2._tileAtmosInfo.MoleDelta = 0; } } } } for (var i = queue.Count - 1; i >= 0; i--) { var tile = queue[i]; if (tile._tileAtmosInfo.CurrentTransferAmount == 0 || tile._tileAtmosInfo.CurrentTransferDirection == Direction.Invalid) { continue; } tile.AdjustEqMovement(tile._tileAtmosInfo.CurrentTransferDirection, tile._tileAtmosInfo.CurrentTransferAmount); if (tile._adjacentTiles.TryGetValue(tile._tileAtmosInfo.CurrentTransferDirection, out var adjacent)) { adjacent._tileAtmosInfo.CurrentTransferAmount += tile._tileAtmosInfo.CurrentTransferAmount; } tile._tileAtmosInfo.CurrentTransferAmount = 0; } } } foreach (var tile in tiles) { tile.FinalizeEq(); } foreach (var tile in tiles) { foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) { continue; } if (tile2?.Air?.Compare(Air) == GasMixture.GasCompareResult.NoExchange) { continue; } _gridAtmosphereComponent.AddActiveTile(tile2); break; } } } }
public bool Intersects(Box2 box, out float distance, out Vector2 hitPos) { hitPos = Vector2.Zero; distance = 0; var tmin = 0.0f; // set to -FLT_MAX to get first hit on line var tmax = float.MaxValue; // set to max distance ray can travel (for segment) const float epsilon = 1.0E-07f; // X axis slab { if (MathF.Abs(_direction.X) < epsilon) { // ray is parallel to this slab, it will never hit unless ray is inside box if (_position.X < MathF.Min(box.Left, box.Right) || _position.X > MathF.Max(box.Left, box.Right)) { return(false); } } // calculate intersection t value of ray with near and far plane of slab var ood = 1.0f / _direction.X; var t1 = (MathF.Min(box.Left, box.Right) - _position.X) * ood; var t2 = (MathF.Max(box.Left, box.Right) - _position.X) * ood; // Make t1 be the intersection with near plane, t2 with far plane if (t1 > t2) { MathHelper.Swap(ref t1, ref t2); } // Compute the intersection of slab intersection intervals tmin = MathF.Max(t1, tmin); tmax = MathF.Min(t2, tmax); // Is this Min (SE) or Max(Textbook) // Exit with no collision as soon as slab intersection becomes empty if (tmin > tmax) { return(false); } } // Y axis slab { if (MathF.Abs(_direction.Y) < epsilon) { // ray is parallel to this slab, it will never hit unless ray is inside box if (_position.Y < MathF.Min(box.Top, box.Bottom) || _position.Y > MathF.Max(box.Top, box.Bottom)) { return(false); } } // calculate intersection t value of ray with near and far plane of slab var ood = 1.0f / _direction.Y; var t1 = (MathF.Min(box.Top, box.Bottom) - _position.Y) * ood; var t2 = (MathF.Max(box.Top, box.Bottom) - _position.Y) * ood; // Make t1 be the intersection with near plane, t2 with far plane if (t1 > t2) { MathHelper.Swap(ref t1, ref t2); } // Compute the intersection of slab intersection intervals tmin = MathF.Max(t1, tmin); tmax = MathF.Min(t2, tmax); // Is this Min (SE) or Max(Textbook) // Exit with no collision as soon as slab intersection becomes empty if (tmin > tmax) { return(false); } } // Ray intersects all slabs. Return point and intersection t value hitPos = _position + _direction * tmin; distance = tmin; return(true); }