# ----------------------------------------------------------------------------
# pymunk
# Copyright (c) 2007-2011 Victor Blomqvist
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ----------------------------------------------------------------------------

"""A constraint is something that describes how two bodies interact with 
each other. (how they constrain each other). Constraints can be simple 
joints that allow bodies to pivot around each other like the bones in your 
body, or they can be more abstract like the gear joint or motors. 

This submodule contain all the constraints that are supported by pymunk.

Chipmunk has a good overview of the different constraint on youtube which 
works fine to showcase them in pymunk as well. 
http://www.youtube.com/watch?v=ZgJJZTS0aMM

.. raw:: html
    
    <iframe width="420" height="315" style="display: block; margin: 0 auto;"
    src="http://www.youtube.com/embed/ZgJJZTS0aMM" frameborder="0" 
    allowfullscreen></iframe>
    
"""
__version__ = "$Id: constraint.py 480 2013-03-03 23:55:20Z vb@viblo.se $"
__docformat__ = "reStructuredText"

import ctypes as ct

from . import _chipmunk as cp 
from . import _chipmunk_ffi as cpffi 

class Constraint(object):
    """Base class of all constraints. 
    
    You usually don't want to create instances of this class directly, but 
    instead use one of the specific constraints such as the PinJoint.
    """
    def __init__(self, constraint=None):
        self._constraint = constraint
        self._ccontents = self._constraint.contents
        
    def _get_max_force(self):
        return self._ccontents.maxForce
    def _set_max_force(self, f):
        self._ccontents.maxForce = f
    max_force = property(_get_max_force, _set_max_force,
        doc="""The maximum force that the constraint can use to act on the two 
        bodies. Defaults to infinity""")
        
    def _get_error_bias(self):
        return self._ccontents.errorBias
    def _set_error_bias(self, error_bias):
        self._ccontents.errorBias = error_bias
    error_bias = property(_get_error_bias, _set_error_bias,
        doc="""The rate at which joint error is corrected.

        Defaults to pow(1.0 - 0.1, 60.0) meaning that it will correct 10% of 
        the error every 1/60th of a second.""")
        
    def _get_max_bias(self):
        return self._ccontents.maxBias
    def _set_max_bias(self, max_bias):
        self._ccontents.maxBias = max_bias
    max_bias = property(_get_max_bias, _set_max_bias,
        doc="""The maximum rate at which joint error is corrected. Defaults 
        to infinity""")

    def _get_impulse(self):
        return cpffi.cpConstraintGetImpulse(self._constraint)
    impulse = property(_get_impulse,
        doc="""Get the last impulse applied by this constraint.""")
        
    a = property(lambda self: self._a, 
        doc="""The first of the two bodies constrained""")
    b = property(lambda self: self._b,
        doc="""The second of the two bodies constrained""")
        
    def activate_bodies(self):
        """Activate the bodies this constraint is attached to"""
        self._a.activate()
        self._b.activate()
    
    def _set_bodies(self, a, b):
        self._a = a
        self._b = b
        a._constraints.add(self)
        b._constraints.add(self)
    
    def __del__(self):
        if cp is not None:
            cp.cpConstraintFree(self._constraint)

class PinJoint(Constraint):
    """Keeps the anchor points at a set distance from one another."""
    def __init__(self, a, b, anchr1=(0,0), anchr2=(0,0)):
        """a and b are the two bodies to connect, and anchr1 and anchr2 are the
        anchor points on those bodies.
        """

        self._constraint = cp.cpPinJointNew(a._body, b._body, anchr1, anchr2)
        self._ccontents = self._constraint.contents
        self._pjc = cp.cast(self._constraint, ct.POINTER(cp.cpPinJoint)).contents
        self._set_bodies(a,b)        
        
    def _get_anchr1(self):
        return self._pjc.anchr1
    def _set_anchr1(self, anchr):
        self._pjc.anchr1 = anchr
    anchr1 = property(_get_anchr1, _set_anchr1)
    
    def _get_anchr2(self):
        return self._pjc.anchr2
    def _set_anchr2(self, anchr):
        self._pjc.anchr2 = anchr
    anchr2 = property(_get_anchr2, _set_anchr2)
    
    def _get_dist(self):
        return self._pjc.dist
    def _set_dist(self, dist):
        self._pjc.dist = dist
    distance = property(_get_dist, _set_dist)
    
class SlideJoint(Constraint):
    """Like pin joints, but have a minimum and maximum distance.
    A chain could be modeled using this joint. It keeps the anchor points 
    from getting to far apart, but will allow them to get closer together.
    """
    def __init__(self, a, b, anchr1, anchr2, min, max):
        """a and b are the two bodies to connect, anchr1 and anchr2 are the
        anchor points on those bodies, and min and max define the allowed
        distances of the anchor points.
        """
        self._constraint = cp.cpSlideJointNew(a._body, b._body, anchr1, anchr2, min, max)
        self._ccontents = self._constraint.contents
        self._sjc = cp.cast(self._constraint, ct.POINTER(cp.cpSlideJoint)).contents
        self._set_bodies(a,b)
        
    def _get_anchr1(self):
        return self._sjc.anchr1
    def _set_anchr1(self, anchr):
        self._sjc.anchr1 = anchr
    anchr1 = property(_get_anchr1, _set_anchr1)
    
    def _get_anchr2(self):
        return self._sjc.anchr2
    def _set_anchr2(self, anchr):
        self._sjc.anchr2 = anchr
    anchr2 = property(_get_anchr2, _set_anchr2)

    def _get_min(self):
        return self._sjc.min
    def _set_min(self, min):
        self._sjc.min = min
    min = property(_get_min, _set_min)
    
    def _get_max(self):
        return self._sjc.max
    def _set_max(self, max):
        self._sjc.max = max
    max = property(_get_max, _set_max)    
        
class PivotJoint(Constraint):
    """Simply allow two objects to pivot about a single point."""
    def __init__(self, a, b, *args):
        """a and b are the two bodies to connect, and pivot is the point in
        world coordinates of the pivot. Because the pivot location is given in
        world coordinates, you must have the bodies moved into the correct
        positions already. 
        Alternatively you can specify the joint based on a pair of anchor 
        points, but make sure you have the bodies in the right place as the 
        joint will fix itself as soon as you start simulating the space. 
        
        That is, either create the joint with PivotJoint(a, b, pivot) or 
        PivotJoint(a, b, anchr1, anchr2).
        
            a : `Body`
                The first of the two bodies
            b : `Body`
                The second of the two bodies
            args : [Vec2d] or [Vec2d,Vec2d]
                Either one pivot point, or two anchor points
        """
        
        if len(args) == 1:
            self._constraint = cp.cpPivotJointNew(a._body, b._body, args[0])
        elif len(args) == 2:
            self._constraint = cp.cpPivotJointNew2(a._body, b._body, args[0], args[1])
        else:
            raise Exception("You must specify either one pivot point or two anchor points")
            
        self._ccontents = self._constraint.contents
        self._pjc = cp.cast(self._constraint, ct.POINTER(cp.cpPivotJoint)).contents
        self._set_bodies(a,b)
    
    def _get_anchr1(self):
        return self._pjc.anchr1
    def _set_anchr1(self, anchr):
        self._pjc.anchr1 = anchr
    anchr1 = property(_get_anchr1, _set_anchr1)
    
    def _get_anchr2(self):
        return self._pjc.anchr2
    def _set_anchr2(self, anchr):
        self._pjc.anchr2 = anchr
    anchr2 = property(_get_anchr2, _set_anchr2)
    
class GrooveJoint(Constraint):
    """Similar to a pivot joint, but one of the anchors is
    on a linear slide instead of being fixed.
    """
    def __init__(self, a, b, groove_a, groove_b, anchr2):
        """The groove goes from groove_a to groove_b on body a, and the pivot 
        is attached to anchr2 on body b. All coordinates are body local. 
        """
        self._constraint = cp.cpGrooveJointNew(a._body, b._body, groove_a, groove_b, anchr2)
        self._ccontents = self._constraint.contents
        self._pjc = cp.cast(self._constraint, ct.POINTER(cp.cpGrooveJoint)).contents
        self._set_bodies(a,b)
        
    def _get_anchr2(self):
        return self._pjc.anchr2
    def _set_anchr2(self, anchr):
        self._pjc.anchr2 = anchr
    anchr2 = property(_get_anchr2, _set_anchr2)
    
    def _get_groove_a(self):
        return self._pjc.grv_a
    groove_a = property(_get_groove_a)
    
    def _get_groove_b(self):
        return self._pjc.grv_b
    groove_b = property(_get_groove_b)
    
class DampedSpring(Constraint):
    """A damped spring"""
    def __init__(self, a, b, anchr1, anchr2, rest_length, stiffness, damping):
        """Defined much like a slide joint. 
        
        :Parameters:
            anchr1 : Vec2d or (x,y)
                Anchor point 1, relative to body a
            anchr2 : Vec2d or (x,y)
                Anchor point 2, relative to body b
            rest_length : float
                The distance the spring wants to be.
            stiffness : float
                The spring constant (Young's modulus). 
            damping : float
                How soft to make the damping of the spring. 
        """
        self._constraint = cp.cpDampedSpringNew(a._body, b._body, anchr1, anchr2, rest_length, stiffness, damping)
        self._ccontents = self._constraint.contents
        self._dsc = cp.cast(self._constraint, ct.POINTER(cp.cpDampedSpring)).contents
        self._set_bodies(a,b)
        
    def _get_anchr1(self):
        return self._dsc.anchr1
    def _set_anchr1(self, anchr):
        self._dsc.anchr1 = anchr
    anchr1 = property(_get_anchr1, _set_anchr1)
    
    def _get_anchr2(self):
        return self._dsc.anchr2
    def _set_anchr2(self, anchr):
        self._dsc.anchr2 = anchr
    anchr2 = property(_get_anchr2, _set_anchr2)

    def _get_rest_length(self):
        return self._dsc.restLength
    def _set_rest_length(self, rest_length):
        self._dsc.restLength = rest_length
    rest_length = property(_get_rest_length, _set_rest_length,
        doc="""The distance the spring wants to be.""")
    
    def _get_stiffness(self):
        return self._dsc.stiffness
    def _set_stiffness(self, stiffness):
        self._dsc.stiffness = stiffness
    stiffness = property(_get_stiffness, _set_stiffness,
        doc="""The spring constant (Young's modulus).""")    
    
    def _get_damping(self):
        return self._dsc.damping
    def _set_damping(self, damping):
        self._dsc.damping = damping
    damping = property(_get_damping, _set_damping, 
        doc="""How soft to make the damping of the spring.""")   
    
class DampedRotarySpring(Constraint):
    """Like a damped spring, but works in an angular fashion"""
    def __init__(self, a, b, rest_angle, stiffness, damping):
        """Like a damped spring, but works in an angular fashion. 
        
        :Parameters:
            rest_angle
                The relative angle in radians that the bodies want to have
            stiffness
                The spring constant (Young's modulus). 
            damping
                How soft to make the damping of the spring. 
        """
        self._constraint = cp.cpDampedRotarySpringNew(a._body, b._body, rest_angle, stiffness, damping)
        self._ccontents = self._constraint.contents
        self._dsc = cp.cast(self._constraint, ct.POINTER(cp.cpDampedRotarySpring)).contents
        self._set_bodies(a,b)
        
    def _get_rest_angle(self):
        return self._dsc.restAngle
    def _set_rest_angle(self, rest_angle):
        self._dsc.restAngle = rest_angle
    rest_angle = property(_get_rest_angle, _set_rest_angle, 
        doc="""The relative angle in radians that the bodies want to have""")
    
    def _get_stiffness(self):
        return self._dsc.stiffness
    def _set_stiffness(self, stiffness):
        self._dsc.stiffness = stiffness
    stiffness = property(_get_stiffness, _set_stiffness,
        doc="""The spring constant (Young's modulus).""")  
    
    def _get_damping(self):
        return self._dsc.damping
    def _set_damping(self, damping):
        self._dsc.damping = damping
    damping = property(_get_damping, _set_damping, 
        doc="""How soft to make the damping of the spring.""") 

    def _set_torque_func(self, func):
        """Set the torque function
            
            func(self, relative_angle) -> torque
            
            Callback Parameters
                relative_angle : float
                    The relative angle
        
        """
        def _impl(_, relative_angle):
            return func(self, relative_angle)
        
        self._torque_func_callback = cp.cpDampedRotarySpringTorqueFunc(_impl)
        self._dsc.springTorqueFunc = self._torque_func_callback
    torque_func = property(fset=_set_torque_func, 
        doc=_set_torque_func.__doc__)
        
        
class RotaryLimitJoint(Constraint):
    """Constrains the relative rotations of two bodies."""
    def __init__(self, a, b, min, max):
        """Constrains the relative rotations of two bodies. min and max are 
        the angular limits in radians. It is implemented so that it's possible 
        to for the range to be greater than a full revolution.
        """
        self._constraint = cp.cpRotaryLimitJointNew(a._body, b._body, min, max)
        self._ccontents = self._constraint.contents
        self._rlc = cp.cast(self._constraint, ct.POINTER(cp.cpRotaryLimitJoint)).contents
        self._set_bodies(a,b)
        
    def _get_min(self):
        return self._rlc.min
    def _set_min(self, min):
        self._rlc.min = min
    min = property(_get_min, _set_min)
    
    def _get_max(self):
        return self._rlc.max
    def _set_max(self, max):
        self._rlc.max = max
    max = property(_get_max, _set_max)    
    
class RatchetJoint(Constraint):
    """Works like a socket wrench."""
    def __init__(self, a, b, phase, ratchet):
        """Works like a socket wrench. ratchet is the distance between 
        "clicks", phase is the initial offset to use when deciding where the 
        ratchet angles are.  
        """
        self._constraint = cp.cpRatchetJointNew(a._body, b._body, phase, ratchet)
        self._ccontents = self._constraint.contents
        self._dsc = cp.cast(self._constraint, ct.POINTER(cp.cpRatchetJoint)).contents
        self._set_bodies(a,b)
        
    def _get_angle(self):
        return self._dsc.angle
    def _set_angle(self, angle):
        self._dsc.angle = angle
    angle = property(_get_angle, _set_angle)
    
    def _get_phase(self):
        return self._dsc.phase
    def _set_phase(self, phase):
        self._dsc.phase = phase
    phase = property(_get_phase, _set_phase)  
    
    def _get_ratchet(self):
        return self._dsc.ratchet
    def _set_ratchet(self, ratchet):
        self._dsc.ratchet = ratchet
    ratchet = property(_get_ratchet, _set_ratchet) 
    
class GearJoint(Constraint):
    """Keeps the angular velocity ratio of a pair of bodies constant."""
    def __init__(self, a, b, phase, ratio):
        """Keeps the angular velocity ratio of a pair of bodies constant. 
        ratio is always measured in absolute terms. It is currently not 
        possible to set the ratio in relation to a third body's angular 
        velocity. phase is the initial angular offset of the two bodies.  
        """
        self._constraint = cp.cpGearJointNew(a._body, b._body, phase, ratio)
        self._ccontents = self._constraint.contents
        self._dsc = cp.cast(self._constraint, ct.POINTER(cp.cpGearJoint)).contents
        self._set_bodies(a,b)
        
    def _get_phase(self):
        return self._dsc.phase
    def _set_phase(self, phase):
        self._dsc.phase = phase
    phase = property(_get_phase, _set_phase)  
    
    def _get_ratio(self):
        return self._dsc.ratio
    def _set_ratio(self, ratio):
        self._dsc.ratio = ratio
    ratio = property(_get_ratio, _set_ratio) 
    
class SimpleMotor(Constraint):
    """Keeps the relative angular velocity of a pair of bodies constant."""
    def __init__(self, a, b, rate):
        """Keeps the relative angular velocity of a pair of bodies constant. 
        rate is the desired relative angular velocity. You will usually want 
        to set an force (torque) maximum for motors as otherwise they will be 
        able to apply a nearly infinite torque to keep the bodies moving.  
        """
        self._constraint = cp.cpSimpleMotorNew(a._body, b._body, rate)
        self._ccontents = self._constraint.contents
        self._dsc = cp.cast(self._constraint, ct.POINTER(cp.cpSimpleMotor)).contents
        self._set_bodies(a,b)
        
    def _get_rate(self):
        return self._dsc.rate
    def _set_rate(self, rate):
        self._dsc.rate = rate
    rate = property(_get_rate, _set_rate,
        doc="""The desired relative angular velocity""")  


