drag.py - drag objects within a plane with accuracy

This script allows you to easily drag objects. There are several options that you can use to customise it, they are as follow:

  • Choose which plane to drag the object in (camera’s, xy, yz, xz)
  • Choose whether to have an object snap to the drag location or move towards it
  • Choose which mouse button to drag with
  • Choose how quickly an dragged object moves (if you have chosen for it to move there)
  • Choose whether a dragged object retains it’s velocity once dragging is completed

These are specified as properties on the object, see the script for details.

This script is also quite accurate, if you click in the corner of a cube when you start dragging you can expect the mouse to be over that exact same spot as you finish dragging.

No other sensors apart from an always sensor are needed. Just add a python controller to the object you want to drag, set the controller mode to module and type in ‘drag.main’ then configure the options as described in the script.

Example: drag.blend

"""
drag.py - drag an object in a plane
andrew-101, June 2011, Blender 2.57

Add this script to an object to be able to use the mouse to
drag that object around. You can choose to either drag the
object parallel to the camera's plan, the xy plane, the yz
plane or the xz plane. You can also choose to instantanously
snap the object to the drag position or have it move towards
it making it aware of collisions (requires dynamic or rigid
body physics type)

Usage:
    1. Add a python controller set to module and set the
    script to be 'drag.main', connect that to a always
    sensor with true level triggering.
    
    2. (optional) add a property 'drag_plane', set it to:
        'cam' to drag parallel to camera's plane (default)
        'xy' to drag parallel to xy plane
        'yz' to drag parallel to yz plane
        'xz' to drag parallel to xz plane
        
    3. (optional) add a property 'drag_type', set to:
        'snap' to snap to drag location for use with static
        body type (default)
        'move' to use force to move to the drag location, for
        use with dynamic/rigid body types
        
    4. (optional) add a property 'drag_event' and set it to
    the event constant (of bge.events) you wish to trigger the
    drag with, e.g:
        'LEFTMOUSE' (default)
        'RIGHTMOUSE'
        'MIDDLEMOUSE'
        
    5. (optional) add a property 'drag_speed' for usage
    with the move type. When being dragged the object will be
    forced to always move slower than this. Default is 15.0
    
    6. (optional) add a property 'drag_retain' and set to:
        True in order to retain velocity when dragging is
        halted.
        False in order to set velocity to 0 when dragging is
        halted. (default)
"""
import math

import bge
from mathutils import *

PLANE_CAM = 'cam'
PLANE_XY = 'xy'
PLANE_YZ = 'yz'
PLANE_XZ = 'xz'
PLANES = { \
    'xy': Vector([0,0,1]),\
    'yz': Vector([1,0,0]),\
    'xz': Vector([0,1,0])\
}
TYPE_SNAP = 'snap'
TYPE_MOVE = 'move'

def get_point_behind_mouse():
    cam = bge.logic.getCurrentScene().active_camera
    m_pos = bge.logic.mouse.position
    
    c = Vector([2*m_pos[0]-1, 2*(1-m_pos[1])-1, 0, 1])
    v = c * cam.projection_matrix.inverted()
    return (v / v.w) * cam.modelview_matrix.inverted()

def get_constrained_position():
    # constrain a point to the plane
    own = bge.logic.getCurrentController().owner
    cam = bge.logic.getCurrentScene().active_camera
    
    p = Vector(get_point_behind_mouse()[:3])
    c = cam.worldPosition
    o = own.worldPosition-own['drag_point']
    
    plane = own.get('drag_plane', 'cam')
    if plane.lower() == PLANE_CAM:
        n = cam.worldOrientation[0].cross(cam.worldOrientation[1])
    else:
        n = PLANES[plane.lower()]
        
    return c + (n.dot(o-c) / n.dot(p-c)) * (p-c) + own['drag_point']

def main(cont):
    own = cont.owner  
    mouse = bge.logic.mouse
    event = getattr(bge.events, own.get('drag_event', 'LEFTMOUSE'))
    
    if not own.get('drag_state'):
        own['drag_state'] = False
        own['drag_distance'] = 0
    
    if own['drag_state']:
        if mouse.events[event] == bge.logic.KX_INPUT_JUST_RELEASED:
            # finish dragging
            own['drag_state'] = False
            if not own.get('drag_retain', True) or own.get('drag_type', 'snap') == 'snap':
                own.worldLinearVelocity = [0,0,0]
        else:
            # update dragging
            drag_pos = get_constrained_position()
            drag_speed = own.get('drag_speed', 15.0)
            type = own.get('drag_type', TYPE_SNAP)
            own.applyForce([0,0,own.mass*9.8]) # cancel effect of gravity while dragging
            if type == TYPE_SNAP:
                own.worldPosition = drag_pos
            elif type == TYPE_MOVE:
                distance = own.getDistanceTo(drag_pos)
                own.applyForce(200*(drag_pos-own.worldPosition))
                vel = own.worldLinearVelocity
                if vel.magnitude > min(5*distance, drag_speed):
                    vel.magnitude = min(5*distance, drag_speed)
                    own.worldLinearVelocity = vel
        
    elif mouse.events[event] == bge.logic.KX_INPUT_JUST_ACTIVATED:
        # initiate dragging
        cam = bge.logic.getCurrentScene().active_camera
        
        p = get_point_behind_mouse()
        hit_obj, hit_pos, hit_norm = cam.rayCast(p[:3], cam, cam.far)
        
        if own == hit_obj:
            own.worldLinearVelocity = [0,0,0]
            own['drag_point'] = own.worldPosition-hit_pos
            own['drag_state'] = True

Attachments

drag.blend (485 KB)

The use of a mouse sensor (with True Pulse) would be more efficient. Always running Python controllers waste resources.

the logic goes from 0.009 ms to 0.190 ms on my computer but this only occur when I drag the object around. this is definelly an awesome resource, thanks andrew this ll help a lot!