cyclone2d 0.1.2

A small 2D physics engine from 'Game Physics Engine Development'
Documentation
use crate::contact_generators::Contact;
use crate::{FP, FP_MAX};

/////////////////////////////////////////////////////////////////////////////////
pub struct ContactResolver {
    iterations: u16,
    iterations_used: u16,
}

impl ContactResolver {
    pub fn new(iterations: u16) -> ContactResolver {
        ContactResolver {
            iterations,
            iterations_used: 0,
        }
    }

    #[inline]
    pub fn set_iterations(&mut self, n: u16) {
        self.iterations = n;
    }

    pub fn resolve_contacts(
        &mut self,
        contacts: &mut [Contact],
        used_contacts: usize,
        dt: FP,
    ) {
        self.iterations_used = 0;
        let mut max = FP_MAX;
        let mut max_index = used_contacts;
        while self.iterations_used < self.iterations {
            // find the contact with largest closing velocity
            for (i, contact) in contacts.iter().enumerate().take(used_contacts)
            {
                let sep_vel = contact.calc_separating_velocity();
                if sep_vel < max && (sep_vel < 0.0 || contact.penetration > 0.0)
                {
                    max = sep_vel;
                    max_index = i;
                }
            }
            if max_index == used_contacts {
                break;
            }
            // resolve this contact
            contacts[max_index].resolve(dt);

            // Update the interpenetrations for all
            let first_move = &contacts[max_index].first_move;
            let second_move = &contacts[max_index].second_move;
            for i in 0..used_contacts {
                if contacts[i].first == contacts[max_index].first {
                    contacts[i].penetration -=
                        *first_move * contacts[i].contact_normal;
                } else if let Some(second) = contacts[max_index].second {
                    if contacts[i].first == second {
                        contacts[i].penetration -=
                            *second_move * contacts[i].contact_normal;
                    }
                }
                if let Some(second_i) = contacts[i].second {
                    if second_i == contacts[max_index].first {
                        contacts[i].penetration +=
                            *first_move * contacts[i].contact_normal;
                    } else if let Some(second_max) = contacts[max_index].second
                    {
                        if second_i == second_max {
                            contacts[i].penetration +=
                                *second_move * contacts[i].contact_normal;
                        }
                    }
                }
            }
            self.iterations_used += 1;
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////

impl Contact {
    /// The main contact resolution method
    #[inline]
    fn resolve(&mut self, dt: FP) {
        self.resolve_velocity(dt);
        self.resolve_interpenetration();
    }

    #[inline]
    fn calc_separating_velocity(&self) -> FP {
        let mut relative_velocity = unsafe { self.first.as_ref().velocity() };
        if let Some(second) = self.second {
            relative_velocity -= unsafe { second.as_ref().velocity() }
        };
        relative_velocity * self.contact_normal
    }

    fn resolve_velocity(&mut self, dt: FP) {
        // Find the velocity in the direction of the contact
        let separating_v = self.calc_separating_velocity();
        // Check if it needs to be resolved
        if separating_v > 0.0 {
            // The contact is either separating, or stationary;
            // no impulse is required.
            return;
        }

        // Calculate the new separating velocity
        let mut new_separating_v = -separating_v * self.restitution;

        // Check the velocity build-up due to acceleration only
        // This process tries to solve resting contacts with
        // micro-collisions
        let mut acc_caused_velocity =
            unsafe { self.first.as_mut().acceleration() };
        if let Some(second) = self.second {
            acc_caused_velocity -= unsafe { second.as_ref().acceleration() };
        }
        let acc_caused_sep_vel = acc_caused_velocity * self.contact_normal * dt;

        // If we've got a closing velocity due to acceleration build-up,
        // remove it from the new separating velocity
        if acc_caused_sep_vel < 0.0 {
            new_separating_v += self.restitution * acc_caused_sep_vel;
            // Make sure we haven't removed more than was
            // there to remove.
            if new_separating_v < 0.0 {
                new_separating_v = 0.0;
            }
        }

        let delta_velocity = new_separating_v - separating_v;

        // We apply the change in velocity to each object in proportion to
        // their inverse mass (i.e. those with lower inverse mass [higher
        // actual mass] get less change in velocity).
        let total_inverse_mass = self.get_total_inverse_mass();
        // If all particles have infinite mass, then impulses have no effect
        if total_inverse_mass <= 0.0 {
            return;
        }

        let impulse = delta_velocity / total_inverse_mass;
        // amount of impulse per inverse mass
        let impulse_per_imass = self.contact_normal * impulse;

        // Apply impulses: they are applied in the direction of the contact,
        // and are proportional to the inverse mass.
        let first = unsafe { self.first.as_mut() };
        first.set_velocity(
            first.velocity() + impulse_per_imass * first.inverse_mass(),
        );

        if let Some(second) = self.second.as_mut() {
            let second = unsafe { second.as_mut() };
            second.set_velocity(
                second.velocity() + impulse_per_imass * -second.inverse_mass(),
            );
        }
    }

    #[inline]
    fn get_total_inverse_mass(&mut self) -> FP {
        let mut total = unsafe { self.first.as_mut().inverse_mass() };
        if let Some(second) = self.second {
            total += unsafe { second.as_ref().inverse_mass() };
        }
        total
    }

    fn resolve_interpenetration(&mut self) {
        // If we don't have any penetration, skip this step
        if self.penetration <= 0.0 {
            return;
        }

        // The movement of each object is based on their inverse mass, so
        // total that.
        let total_inverse_mass = self.get_total_inverse_mass();
        // If all particles have infinite mass, then impulses have no effect
        if total_inverse_mass <= 0.0 {
            return;
        }

        // Find the amount of penetration resolution per unit of inverse mass
        let move_per_imass =
            self.contact_normal * (self.penetration / total_inverse_mass);

        // Calculate the the movement amounts
        self.first_move =
            move_per_imass * unsafe { self.first.as_ref().inverse_mass() };
        if let Some(second) = self.second {
            self.second_move =
                move_per_imass * unsafe { -second.as_ref().inverse_mass() };
        } else {
            self.second_move.clear();
        }

        // Apply penetration resolution to positions
        let first = unsafe { self.first.as_mut() };
        first.set_position(first.position() + self.first_move);
        if let Some(second) = self.second.as_mut() {
            let second = unsafe { second.as_mut() };
            second.set_position(second.position() + self.second_move);
        };
    }
}