Ruling ship-bounce-restitution-setting: j = -(1+e)(v_rel.n)/(1/m1+1/m2), applied only while closing. The old exchange formula (PostCollisionVelocity) is deleted. ContactTests now pin momentum, frame shift, restitution, no impulse while separating, slow closing, tangent of both bodies, inactive hulls, the contact point radius and the three-body pile. Not yet built or run; verification follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.8 KiB
C#
79 lines
3.8 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
using System.Linq;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
using float2 = CultMath.float2;
|
|
|
|
// A hull touching a hull (aetheria-release cut sim-contact; ruling unity-presents-only-r3): the simulation owns
|
|
// contact between entities. Each hull is a disc about its centre of mass that bounds every occupied cell, and
|
|
// two discs that touch while closing along the line between their centres take the normal impulse
|
|
// j = -(1 + e)(v_rel . n) / (1/m1 + 1/m2) (ruling ship-bounce-restitution-setting): momentum is conserved, the
|
|
// result is frame-independent, and a pair that has separated is not bounced again. e is
|
|
// GameplaySettings.ContactRestitution.
|
|
// Presentations read Zone.Contacted; no Unity collision callback writes an Entity.
|
|
public readonly struct ContactEvent
|
|
{
|
|
public readonly Entity A;
|
|
public readonly Entity B;
|
|
public readonly float2 Point;
|
|
// Unit vector from A to B.
|
|
public readonly float2 Normal;
|
|
|
|
public ContactEvent(Entity a, Entity b, float2 point, float2 normal)
|
|
{
|
|
A = a;
|
|
B = b;
|
|
Point = point;
|
|
Normal = normal;
|
|
}
|
|
}
|
|
|
|
public static class Contacts
|
|
{
|
|
// The farthest corner of any occupied hull cell from the centre of mass the schematic frame uses, in world
|
|
// units: a true bound on how far the hull reaches from Position (the half-diagonal of the bounding box is not,
|
|
// as FireControl.Detonate's comment records). A cell at (x, y) spans x +/- .5 and y +/- .5.
|
|
public static float Radius(Entity e)
|
|
{
|
|
var shape = e.HullData.Shape;
|
|
var com = shape.CenterOfMass;
|
|
var far = 0f;
|
|
foreach (var c in shape.Coordinates)
|
|
foreach (var (dx, dy) in new[] { (-.5f, -.5f), (.5f, -.5f), (-.5f, .5f), (.5f, .5f) })
|
|
far = max(far, length(float2(c.x + dx, c.y + dy) - com));
|
|
return far * e.ItemManager.GameplaySettings.SchematicCellSize;
|
|
}
|
|
|
|
// For each unordered pair of the zone's entities, in list order: if their discs touch and are closing along
|
|
// the normal, the impulse moves each one's velocity along the normal only (the tangent components are
|
|
// untouched), and the contact is published. Pairs resolve one after another in list order, each from the
|
|
// velocities the earlier pairs left, so a body touching two others in one tick depends on that order; the
|
|
// total momentum does not. A docked entity (it has a Parent and takes its parent's pose) and an inactive one
|
|
// are not hulls in the zone. No positional push-out.
|
|
public static void Step(Zone zone)
|
|
{
|
|
var entities = zone.Entities.Where(e => e.Active && e.Parent == null).ToArray();
|
|
var radii = entities.Select(Radius).ToArray();
|
|
for (var i = 0; i < entities.Length; i++)
|
|
for (var j = i + 1; j < entities.Length; j++)
|
|
{
|
|
var a = entities[i];
|
|
var b = entities[j];
|
|
var offset = b.Position.xz - a.Position.xz;
|
|
var distance = length(offset);
|
|
// Coincident centres have no normal.
|
|
if (distance <= 0f || distance >= radii[i] + radii[j]) continue;
|
|
var n = offset / distance;
|
|
var closing = dot(n, b.Velocity - a.Velocity);
|
|
if (closing >= 0f) continue;
|
|
|
|
var impulse = -(1f + a.ItemManager.GameplaySettings.ContactRestitution) * closing / (1f / a.Mass + 1f / b.Mass);
|
|
a.Velocity -= n * (impulse / a.Mass);
|
|
b.Velocity += n * (impulse / b.Mass);
|
|
zone.Contacted.OnNext(new ContactEvent(a, b, a.Position.xz + n * radii[i], n));
|
|
}
|
|
}
|
|
}
|