DiamondDebris.Bounds.cs
using System;
using Sandbox;
using static Diamonds.DiamondBoard;
namespace Diamonds;
public sealed partial class DiamondDebris
{
void ConstrainParticles()
{
#if STANDALONE
#endif
foreach ( var chip in particles )
{
var body = chip.Body;
if ( body.Sleeping ) continue;
var velocity = body.Velocity;
// Fast keyframed gems and crowded seams can impart huge solver impulses.
// Bound those kicks as well as normal flight, before and after each step.
float maxSpeed = chip.IsSpark ? MaxSparkSpeed : 1000;
if ( velocity.LengthSquared > maxSpeed * maxSpeed )
{
velocity = velocity.Normal * maxSpeed;
body.Velocity = velocity;
}
if ( chip.IsSpark ) continue;
// Solid walls handle ordinary bounces. Correct any solver penetration
// using the rotated triangle, so even its tips stay inside the canvas.
float minX = float.PositiveInfinity, maxX = float.NegativeInfinity, minY = float.PositiveInfinity;
float floorPenetration = 0;
var position = body.Position;
var rotation = body.Rotation;
for ( int i = 0; i < chip.Vertices.Length; i++ )
{
var vertex = chip.Vertices[i];
var point = position + rotation * new Vector3( vertex.x, vertex.y, 0 );
Accumulate( point );
AccumulateFloor( point );
minY = MathF.Min( minY, point.y );
var next = chip.Vertices[(i + 1) % chip.Vertices.Length];
var end = position + rotation * new Vector3( next.x, next.y, 0 );
// A triangle edge can cross a tooth tip even when both vertices clear it.
for ( float y = BoardHeight; y + Height * 0.5f > 0; y -= Height )
{
if ( y <= MathF.Min( point.y, end.y ) || y >= MathF.Max( point.y, end.y ) ) continue;
Accumulate( point + (end - point) * ((y - point.y) / (end.y - point.y)) );
}
float cornerY = BoardHeight - Height * 0.5f;
if ( cornerY > MathF.Min( point.y, end.y ) && cornerY < MathF.Max( point.y, end.y ) )
Accumulate( point + (end - point) * ((cornerY - point.y) / (end.y - point.y)) );
// Check floor peaks crossed by an edge as well as its endpoints.
for ( float x = MathF.Ceiling( MathF.Min( point.x, end.x ) / Width ) * Width;
x < MathF.Max( point.x, end.x ); x += Width )
{
if ( x <= MathF.Min( point.x, end.x ) ) continue;
AccumulateFloor( point + (end - point) * ((x - point.x) / (end.x - point.x)) );
}
}
void AccumulateFloor( Vector3 point )
{
float x = Math.Clamp( point.x, 0, BoardWidth );
float floor = BoardHeight - Height * 0.5f * MathF.Abs( 1 - x / (Width * 0.5f) % 2 );
floorPenetration = MathF.Max( floorPenetration, point.y - floor );
}
void Accumulate( Vector3 point )
{
float inset = SideWallInset( point.y, FillCornerPockets );
minX = MathF.Min( minX, point.x - inset );
maxX = MathF.Max( maxX, point.x + inset );
}
float dx = minX < 0 ? -minX : maxX > BoardWidth ? BoardWidth - maxX : 0;
// Leave ordinary contact slop to the solver so resting piles can sleep.
float dy = minY < 0 ? -minY : floorPenetration > 0.5f ? -floorPenetration : 0;
if ( dx == 0 && dy == 0 ) continue;
body.Position += new Vector3( dx, dy, 0 );
if ( dx * velocity.x < 0 ) velocity.x *= -0.24f;
if ( dy * velocity.y < 0 ) velocity.y *= -0.24f;
body.Velocity = velocity;
}
}
}