Source

ars / ars / model / collision / adapters.py

Full commit
"""Classes and functions to interface with the collision library
included in ODE.

"""
from abc import ABCMeta

import ode

from . import base
from . import ode_objects_factories
from .base import NearCallbackArgs

#==============================================================================
# Environment
#==============================================================================


class Engine(base.Engine):

	"""Adapter to the ODE collision engine."""

	#==========================================================================
	# Functions and methods not overriding base class functions and methods
	#==========================================================================

	@classmethod
	def near_callback(cls, args, geom1, geom2):
		"""Callback function for the collide() method (in ODE). This function
		checks if the given geoms do collide and creates contact joints (c_joint)
		if they do, except if they are connected."""

		# Contact joint parameters:
		# 	-bounce: default=0.2
		# 	-mu: default=500.
		# 		very slippery 0-5, normal 50-500, very sticky 5000
		c_joint_bounce = 0.2
		c_joint_mu = 500

		world = args.world._inner_object
		contact_group = args.contact_group._inner_object
		ray_geom = None
		other_geom = None

		if args.ignore_connected and cls.are_geoms_connected(geom1, geom2):
			return

		#======================================================================
		# Ray's special case
		#======================================================================
		if cls.is_ray(geom1):
			if cls.is_ray(geom2):
				print('Weird, ODE says two rays may collide. '
				      'That case is not handled.')
				return
			else:
				ray_geom = geom1
				other_geom = geom2

		elif cls.is_ray(geom2):
			ray_geom = geom2
			other_geom = geom1

		#======================================================================
		# create contact joints
		#======================================================================
		# check if the objects collide
		contacts = ode.collide(geom1, geom2)
		for c in contacts:

			if ray_geom is not None:
				cls.process_ray_collision(ray_geom, other_geom, c)
			else:  # we create a ContactJoint only if both geoms are not rays
				# set contact parameters
				c.setBounce(c_joint_bounce)
				c.setMu(c_joint_mu)
				# create contact joints
				j = ode.ContactJoint(world, contact_group, c)
				j.attach(geom1.getBody(), geom2.getBody())

	@classmethod
	def are_geoms_connected(cls, geom1, geom2):
		"""Are `geom1` and `geom2` connected (through the bodies they are
		attached to)?
		"""
		return ode.areConnected(geom1.getBody(), geom2.getBody())

	@classmethod
	def is_ray(cls, geom):
		"""Return whether ``geom`` is a :class:`ode.GeomRay` object or not.

		:param geom:
		:type geom: :class:`ode.GeomObject`
		:return: True if ``geom`` is an instance of :class:`ode.GeomRay`
		:rtype: bool

		"""
		return isinstance(geom, ode.GeomRay)

	@classmethod
	def process_ray_collision(cls, ray, other_geom, contact):
		# pos: intersection position
		# depth: distance
		(pos, normal, depth, geom1, geom2) = contact.getContactGeomParams()
		ray_contact = base.RayContactData(
			ray, other_geom, pos, normal, depth)
		try:
			ray.outer_object.set_last_contact(ray_contact)
		except AttributeError:
			print("`ray` has no attribute `outer_object` ")
		except Exception:
			print("Ray's encapsulating object's last_contact attribute could not be set")


class Space(base.Space):

	"""Adapter to :class:`ode.SimpleSpace`."""

	def __init__(self):
		self._inner_object = ode_objects_factories.create_ode_simple_space()

	def collide(self, args, callback=None):
		if callback is None:
			self._inner_object.collide(args, Engine.near_callback)
		else:
			self._inner_object.collide(args, callback)

#==============================================================================
# Parents
#==============================================================================


class Geom(base.Geom):

	"""Abstract class, sort of equivalent to :class:`ode.GeomObject`."""

	def attach_body(self, body):
		super(Geom, self).attach_body(body)
		self._inner_object.setBody(body.inner_object)

	#==========================================================================
	# Getters and setters
	#==========================================================================

	def get_position(self):
		"""Get the position of the geom.

		:return: position
		:rtype: 3-sequence of floats

		"""
		return self._inner_object.getPosition()

	def get_rotation(self):
		"""Get the orientation of the geom.

		:return: rotation matrix
		:rtype: 9-sequence of floats

		"""
		return self._inner_object.getRotation()

	def set_position(self, pos):
		"""Set the position of the geom.

		:param pos: position
		:type pos: 3-sequence of floats

		"""
		self._inner_object.setPosition(pos)

	def set_rotation(self, rot):
		"""Set the orientation of the geom.

		:param rot: rotation matrix
		:type rot: 9-sequence of floats

		"""
		self._inner_object.setRotation(rot)


class BasicShape(Geom):

	__metaclass__ = ABCMeta

#==============================================================================
# Other shapes
#==============================================================================


class Plane(BasicShape, base.Plane):

	"""Plane, different from a box"""

	def __init__(self, space, normal, dist):
		BasicShape.__init__(self)
		base.Plane.__init__(self, space, normal, dist)

		self._inner_object = ode_objects_factories.create_ode_plane(
			space.inner_object, normal, dist)


class Ray(Geom, base.Ray):

	def __init__(self, space, length):
		Geom.__init__(self)
		base.Ray.__init__(self, space, length)
		self._inner_object = ode_objects_factories.create_ode_ray(
			space.inner_object, length)
		self._inner_object.outer_object = self  # the encapsulating object

	def get_length(self):
		return self._inner_object.getLength()

	def set_length(self, length):
		self._inner_object.setLength(length)


class Trimesh(Geom, base.Trimesh):

	def __init__(self, space, vertices, faces, center):
		Geom.__init__(self)
		base.Trimesh.__init__(self, space, vertices, faces, center)

		self._inner_object = ode_objects_factories.create_ode_trimesh(
			space.inner_object, vertices, faces)
		# FIXME: setting pos here is not consistent with the other constructors
		self.set_position(center)

#==============================================================================
# Basic Shapes
#==============================================================================


class Box(BasicShape, base.Box):
	"""Box shape, aligned along the X, Y and Z axii by default"""

	def __init__(self, space, size):
		BasicShape.__init__(self)
		base.Box.__init__(self, space, size)

		self._inner_object = ode_objects_factories.create_ode_box(
			space.inner_object, size)


class Sphere(BasicShape, base.Sphere):
	"""Spherical shape"""

	def __init__(self, space, radius):
		BasicShape.__init__(self)
		base.Sphere.__init__(self, space, radius)

		self._inner_object = ode_objects_factories.create_ode_sphere(
			space.inner_object, radius)


class Capsule(BasicShape, base.Capsule):
	"""Capsule shape, aligned along the Z-axis by default"""

	def __init__(self, space, length, radius):
		BasicShape.__init__(self)
		base.Capsule.__init__(self, space, length, radius)

		self._inner_object = ode_objects_factories.create_ode_capsule(
			space.inner_object, length, radius)


class Cylinder(BasicShape, base.Cylinder):
	"""Cylinder shape, aligned along the Z-axis by default"""

	def __init__(self, space, length, radius):
		BasicShape.__init__(self)
		base.Cylinder.__init__(self, space, length, radius)

		self._inner_object = ode_objects_factories.create_ode_cylinder(
			space.inner_object, length, radius)

#==============================================================================
# aux classes
#==============================================================================


class ContactGroup(base.ContactGroup):

	def __init__(self):
		self._inner_object = ode_objects_factories.create_ode_joint_group()

	def empty(self):
		self._inner_object.empty()