# HG changeset patch # User KarstenBock@gmx.net # Date 1326390168 -3600 # Node ID a6bbb732b27b902baed40c395c0e494135327869 # Parent 95461b06bac16531a459605bf0714e9f5308e16c Added .hgeol file to automatically convert line endings. diff -r 95461b06bac1 -r a6bbb732b27b .hgeol --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgeol Thu Jan 12 18:42:48 2012 +0100 @@ -0,0 +1,4 @@ +[patterns] +**.py = native +**.txt = native +**.cfg = native \ No newline at end of file diff -r 95461b06bac1 -r a6bbb732b27b bGrease/__init__.py --- a/bGrease/__init__.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/__init__.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,75 +1,75 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# 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. -# -############################################################################# - -__versioninfo__ = (0, 3, 0) -__version__ = '.'.join(str(n) for n in __versioninfo__) - -__all__ = ('BaseWorld', 'Entity', 'System', 'Renderer') - -import component -import geometry -import collision -from entity import Entity -from world import BaseWorld - -import abc - -class System(object): - """Grease system abstract base class. Systems define behaviorial aspects - of a |World|. All systems must define a :meth:`step` - method that is invoked by the world each timestep. User-defined systems - are not required to subclass this class. - - See :ref:`an example system from the tutorial `. - """ - __metaclass__ = abc.ABCMeta - - world = None - """The |World| this system belongs to""" - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - @abc.abstractmethod - def step(self, dt): - """Execute a time step for the system. Must be defined - by all system classes. - - :param dt: Time since last step invocation - :type dt: float - """ - -class Renderer(object): - """Grease renderer abstract base class. Renderers define the presentation - of a |World|. All renderers must define a :meth:`draw` - method that is invoked by the world when the display needs to be redrawn. - User-defined renderers are not required to subclass this class. - - See :ref:`an example renderer from the tutorial `. - """ - __metaclass__ = abc.ABCMeta - - world = None - """The |World| this renderer belongs to""" - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - @abc.abstractmethod - def draw(self): - """Issue drawing commands for this renderer. Must be defined - for all renderer classes. - """ - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# 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. +# +############################################################################# + +__versioninfo__ = (0, 3, 0) +__version__ = '.'.join(str(n) for n in __versioninfo__) + +__all__ = ('BaseWorld', 'Entity', 'System', 'Renderer') + +import component +import geometry +import collision +from entity import Entity +from world import BaseWorld + +import abc + +class System(object): + """Grease system abstract base class. Systems define behaviorial aspects + of a |World|. All systems must define a :meth:`step` + method that is invoked by the world each timestep. User-defined systems + are not required to subclass this class. + + See :ref:`an example system from the tutorial `. + """ + __metaclass__ = abc.ABCMeta + + world = None + """The |World| this system belongs to""" + + def set_world(self, world): + """Bind the system to a world""" + self.world = world + + @abc.abstractmethod + def step(self, dt): + """Execute a time step for the system. Must be defined + by all system classes. + + :param dt: Time since last step invocation + :type dt: float + """ + +class Renderer(object): + """Grease renderer abstract base class. Renderers define the presentation + of a |World|. All renderers must define a :meth:`draw` + method that is invoked by the world when the display needs to be redrawn. + User-defined renderers are not required to subclass this class. + + See :ref:`an example renderer from the tutorial `. + """ + __metaclass__ = abc.ABCMeta + + world = None + """The |World| this renderer belongs to""" + + def set_world(self, world): + """Bind the system to a world""" + self.world = world + + @abc.abstractmethod + def draw(self): + """Issue drawing commands for this renderer. Must be defined + for all renderer classes. + """ + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/color.py --- a/bGrease/color.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/color.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,64 +1,64 @@ - -class RGBA(object): - """Four channel color representation. - - RGBA colors are floating point color representations with color channel - values between (0..1). Colors may be initialized from 3 or 4 floating - point numbers or a hex string:: - - RGBA(1.0, 1.0, 1.0) # Alpha defaults to 1.0 - RGBA(1.0, 1.0, 0, 0.5) - RGBA("#333") - RGBA("#7F7F7F") - - Individual color channels can be accessed by attribute name, or the - color object can be treated as a sequence of 4 floats. - """ - - def __init__(self, r_or_colorstr, g=None, b=None, a=None): - if isinstance(r_or_colorstr, str): - assert g is b is a is None, "Ambiguous color arguments" - self.r, self.g, self.b, self.a = self._parse_colorstr(r_or_colorstr) - elif g is b is a is None: - try: - self.r, self.g, self.b, self.a = r_or_colorstr - except ValueError: - self.r, self.g, self.b = r_or_colorstr - self.a = 1.0 - else: - self.r = r_or_colorstr - self.g = g - self.b = b - self.a = a - if self.a is None: - self.a = 1.0 - - def _parse_colorstr(self, colorstr): - length = len(colorstr) - if not colorstr.startswith("#") or length not in (4, 5, 7, 9): - raise ValueError("Invalid color string: " + colorstr) - if length <= 5: - parsed = [int(c*2, 16) / 255.0 for c in colorstr[1:]] - else: - parsed = [int(colorstr[i:i+2], 16) / 255.0 for i in range(1, length, 2)] - if len(parsed) == 3: - parsed.append(1.0) - return parsed - - def __len__(self): - return 4 - - def __getitem__(self, item): - return (self.r, self.g, self.b, self.a)[item] - - def __iter__(self): - return iter((self.r, self.g, self.b, self.a)) - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __repr__(self): - return "%s(%.2f, %.2f, %.2f, %.2f)" % (self.__class__.__name__, - self.r, self.g, self.b, self.a) - - + +class RGBA(object): + """Four channel color representation. + + RGBA colors are floating point color representations with color channel + values between (0..1). Colors may be initialized from 3 or 4 floating + point numbers or a hex string:: + + RGBA(1.0, 1.0, 1.0) # Alpha defaults to 1.0 + RGBA(1.0, 1.0, 0, 0.5) + RGBA("#333") + RGBA("#7F7F7F") + + Individual color channels can be accessed by attribute name, or the + color object can be treated as a sequence of 4 floats. + """ + + def __init__(self, r_or_colorstr, g=None, b=None, a=None): + if isinstance(r_or_colorstr, str): + assert g is b is a is None, "Ambiguous color arguments" + self.r, self.g, self.b, self.a = self._parse_colorstr(r_or_colorstr) + elif g is b is a is None: + try: + self.r, self.g, self.b, self.a = r_or_colorstr + except ValueError: + self.r, self.g, self.b = r_or_colorstr + self.a = 1.0 + else: + self.r = r_or_colorstr + self.g = g + self.b = b + self.a = a + if self.a is None: + self.a = 1.0 + + def _parse_colorstr(self, colorstr): + length = len(colorstr) + if not colorstr.startswith("#") or length not in (4, 5, 7, 9): + raise ValueError("Invalid color string: " + colorstr) + if length <= 5: + parsed = [int(c*2, 16) / 255.0 for c in colorstr[1:]] + else: + parsed = [int(colorstr[i:i+2], 16) / 255.0 for i in range(1, length, 2)] + if len(parsed) == 3: + parsed.append(1.0) + return parsed + + def __len__(self): + return 4 + + def __getitem__(self, item): + return (self.r, self.g, self.b, self.a)[item] + + def __iter__(self): + return iter((self.r, self.g, self.b, self.a)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __repr__(self): + return "%s(%.2f, %.2f, %.2f, %.2f)" % (self.__class__.__name__, + self.r, self.g, self.b, self.a) + + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/component/base.py --- a/bGrease/component/base.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/component/base.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,74 +1,74 @@ -class ComponentBase(object): - """Component abstract base class - - Strictly speaking you do not need to derive from this class to create your - own components, but it does serve to document the full interface that a - component implements and it provides some basic implementations for - certain methods - """ - - ## Optional attributes and methods ## - - def set_manager(self, manager): - """Set the manager of this component. If this method exists it will be - automatically called when the component is added to a manager. - - This method stores the manager and allows the component to be added - only once to a single manager. - """ - assert getattr(self, 'manager', None) is None, 'Component cannot be added to multiple managers' - self.manager = manager - - def __del__(self): - """Break circrefs to allow faster collection""" - if hasattr(self, 'manager'): - del self.manager - - ## Mandatory methods ## - - def add(self, entity_id, data=None, **data_kw): - """Add a data entry in the component for the given entity. Additional - data (if any) for the entry can be provided in the data argument or as - keyword arguments. Additional data is optional and if omitted then - suitable defaults will be used. Return an entity data object - for the new entity entry. - - The semantics of the data arguments is up to the component. - - An entity_id is a unique key, thus multiple separate data entries for - a given entity are not allowed. Components can indivdually decide - what to do if an entity_id is added multiple times to the component. - Potential options include, raising an exception, replacing the - existing data or coalescing it somehow. - """ - - def remove(self, entity_id): - """Remove the entity data entry from the component. If the - entity is not in the component, raise KeyError - """ - - def __delitem_(self, entity_id): - """Same as remove()""" - - def __len__(self): - """Return the number of entities in the component""" - raise NotImplementedError() - - def __iter__(self): - """Return an iterator of entity data objects in this component - - No order is defined for these data objects - """ - raise NotImplementedError() - - def __contains__(self, entity_id): - """Return True if the entity is contained in the component""" - raise NotImplementedError() - - def __getitem__(self, entity_id): - """Return the entity data object for the given entity. - The entity data object returned may be mutable, immutable or a - mutable copy of the data at the discretion of the component - """ - raise NotImplementedError() - +class ComponentBase(object): + """Component abstract base class + + Strictly speaking you do not need to derive from this class to create your + own components, but it does serve to document the full interface that a + component implements and it provides some basic implementations for + certain methods + """ + + ## Optional attributes and methods ## + + def set_manager(self, manager): + """Set the manager of this component. If this method exists it will be + automatically called when the component is added to a manager. + + This method stores the manager and allows the component to be added + only once to a single manager. + """ + assert getattr(self, 'manager', None) is None, 'Component cannot be added to multiple managers' + self.manager = manager + + def __del__(self): + """Break circrefs to allow faster collection""" + if hasattr(self, 'manager'): + del self.manager + + ## Mandatory methods ## + + def add(self, entity_id, data=None, **data_kw): + """Add a data entry in the component for the given entity. Additional + data (if any) for the entry can be provided in the data argument or as + keyword arguments. Additional data is optional and if omitted then + suitable defaults will be used. Return an entity data object + for the new entity entry. + + The semantics of the data arguments is up to the component. + + An entity_id is a unique key, thus multiple separate data entries for + a given entity are not allowed. Components can indivdually decide + what to do if an entity_id is added multiple times to the component. + Potential options include, raising an exception, replacing the + existing data or coalescing it somehow. + """ + + def remove(self, entity_id): + """Remove the entity data entry from the component. If the + entity is not in the component, raise KeyError + """ + + def __delitem_(self, entity_id): + """Same as remove()""" + + def __len__(self): + """Return the number of entities in the component""" + raise NotImplementedError() + + def __iter__(self): + """Return an iterator of entity data objects in this component + + No order is defined for these data objects + """ + raise NotImplementedError() + + def __contains__(self, entity_id): + """Return True if the entity is contained in the component""" + raise NotImplementedError() + + def __getitem__(self, entity_id): + """Return the entity data object for the given entity. + The entity data object returned may be mutable, immutable or a + mutable copy of the data at the discretion of the component + """ + raise NotImplementedError() + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/component/schema.py --- a/bGrease/component/schema.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/component/schema.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,1 +1,1 @@ - + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/controller/integrator.py --- a/bGrease/controller/integrator.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/controller/integrator.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,42 +1,42 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# 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. -# -############################################################################# - -__version__ = '$Id$' - - -class EulerMovement(object): - """System that applies entity movement to position using Euler's method - - :param position_component: Name of :class:`grease.component.Position` - component to update. - :param movement_component: Name of :class:`grease.component.Movement` - component used to update position. - """ - - def __init__(self, position_component='position', movement_component='movement'): - self.position_component = position_component - self.movement_component = movement_component - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - def step(self, dt): - """Apply movement to position""" - assert self.world is not None, "Cannot run with no world set" - for position, movement in self.world.components.join( - self.position_component, self.movement_component): - movement.velocity += movement.accel * dt - position.position += movement.velocity * dt - position.angle += movement.rotation * dt - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# 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. +# +############################################################################# + +__version__ = '$Id$' + + +class EulerMovement(object): + """System that applies entity movement to position using Euler's method + + :param position_component: Name of :class:`grease.component.Position` + component to update. + :param movement_component: Name of :class:`grease.component.Movement` + component used to update position. + """ + + def __init__(self, position_component='position', movement_component='movement'): + self.position_component = position_component + self.movement_component = movement_component + + def set_world(self, world): + """Bind the system to a world""" + self.world = world + + def step(self, dt): + """Apply movement to position""" + assert self.world is not None, "Cannot run with no world set" + for position, movement in self.world.components.join( + self.position_component, self.movement_component): + movement.velocity += movement.accel * dt + position.position += movement.velocity * dt + position.angle += movement.rotation * dt + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/geometry.py --- a/bGrease/geometry.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/geometry.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,600 +1,600 @@ -__version__ = "$Id$" -__docformat__ = "reStructuredText" - -import operator -import math -import ctypes - -class Vec2d(ctypes.Structure): - """2d vector class, supports vector and scalar operators, - and also provides a bunch of high level functions - """ - __slots__ = ['x', 'y'] - - @classmethod - def from_param(cls, arg): - return cls(arg) - - def __init__(self, x_or_pair, y = None): - - if y == None: - self.x = x_or_pair[0] - self.y = x_or_pair[1] - else: - self.x = x_or_pair - self.y = y - - def __len__(self): - return 2 - - def __getitem__(self, key): - if key == 0: - return self.x - elif key == 1: - return self.y - else: - raise IndexError("Invalid subscript "+str(key)+" to Vec2d") - - def __setitem__(self, key, value): - if key == 0: - self.x = value - elif key == 1: - self.y = value - else: - raise IndexError("Invalid subscript "+str(key)+" to Vec2d") - - # String representaion (for debugging) - def __repr__(self): - return 'Vec2d(%s, %s)' % (self.x, self.y) - - # Comparison - def __eq__(self, other): - if hasattr(other, "__getitem__") and len(other) == 2: - return self.x == other[0] and self.y == other[1] - else: - return False - - def __ne__(self, other): - if hasattr(other, "__getitem__") and len(other) == 2: - return self.x != other[0] or self.y != other[1] - else: - return True - - def __nonzero__(self): - return self.x or self.y - - # Generic operator handlers - def _o2(self, other, f): - "Any two-operator operation where the left operand is a Vec2d" - if isinstance(other, Vec2d): - return Vec2d(f(self.x, other.x), - f(self.y, other.y)) - elif (hasattr(other, "__getitem__")): - return Vec2d(f(self.x, other[0]), - f(self.y, other[1])) - else: - return Vec2d(f(self.x, other), - f(self.y, other)) - - def _r_o2(self, other, f): - "Any two-operator operation where the right operand is a Vec2d" - if (hasattr(other, "__getitem__")): - return Vec2d(f(other[0], self.x), - f(other[1], self.y)) - else: - return Vec2d(f(other, self.x), - f(other, self.y)) - - def _io(self, other, f): - "inplace operator" - if (hasattr(other, "__getitem__")): - self.x = f(self.x, other[0]) - self.y = f(self.y, other[1]) - else: - self.x = f(self.x, other) - self.y = f(self.y, other) - return self - - # Addition - def __add__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x + other.x, self.y + other.y) - elif hasattr(other, "__getitem__"): - return Vec2d(self.x + other[0], self.y + other[1]) - else: - return Vec2d(self.x + other, self.y + other) - __radd__ = __add__ - - def __iadd__(self, other): - if isinstance(other, Vec2d): - self.x += other.x - self.y += other.y - elif hasattr(other, "__getitem__"): - self.x += other[0] - self.y += other[1] - else: - self.x += other - self.y += other - return self - - # Subtraction - def __sub__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x - other.x, self.y - other.y) - elif (hasattr(other, "__getitem__")): - return Vec2d(self.x - other[0], self.y - other[1]) - else: - return Vec2d(self.x - other, self.y - other) - def __rsub__(self, other): - if isinstance(other, Vec2d): - return Vec2d(other.x - self.x, other.y - self.y) - if (hasattr(other, "__getitem__")): - return Vec2d(other[0] - self.x, other[1] - self.y) - else: - return Vec2d(other - self.x, other - self.y) - def __isub__(self, other): - if isinstance(other, Vec2d): - self.x -= other.x - self.y -= other.y - elif (hasattr(other, "__getitem__")): - self.x -= other[0] - self.y -= other[1] - else: - self.x -= other - self.y -= other - return self - - # Multiplication - def __mul__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x*other.y, self.y*other.y) - if (hasattr(other, "__getitem__")): - return Vec2d(self.x*other[0], self.y*other[1]) - else: - return Vec2d(self.x*other, self.y*other) - __rmul__ = __mul__ - - def __imul__(self, other): - if isinstance(other, Vec2d): - self.x *= other.x - self.y *= other.y - elif (hasattr(other, "__getitem__")): - self.x *= other[0] - self.y *= other[1] - else: - self.x *= other - self.y *= other - return self - - # Division - def __div__(self, other): - return self._o2(other, operator.div) - def __rdiv__(self, other): - return self._r_o2(other, operator.div) - def __idiv__(self, other): - return self._io(other, operator.div) - - def __floordiv__(self, other): - return self._o2(other, operator.floordiv) - def __rfloordiv__(self, other): - return self._r_o2(other, operator.floordiv) - def __ifloordiv__(self, other): - return self._io(other, operator.floordiv) - - def __truediv__(self, other): - return self._o2(other, operator.truediv) - def __rtruediv__(self, other): - return self._r_o2(other, operator.truediv) - def __itruediv__(self, other): - return self._io(other, operator.floordiv) - - # Modulo - def __mod__(self, other): - return self._o2(other, operator.mod) - def __rmod__(self, other): - return self._r_o2(other, operator.mod) - - def __divmod__(self, other): - return self._o2(other, divmod) - def __rdivmod__(self, other): - return self._r_o2(other, divmod) - - # Exponentation - def __pow__(self, other): - return self._o2(other, operator.pow) - def __rpow__(self, other): - return self._r_o2(other, operator.pow) - - # Bitwise operators - def __lshift__(self, other): - return self._o2(other, operator.lshift) - def __rlshift__(self, other): - return self._r_o2(other, operator.lshift) - - def __rshift__(self, other): - return self._o2(other, operator.rshift) - def __rrshift__(self, other): - return self._r_o2(other, operator.rshift) - - def __and__(self, other): - return self._o2(other, operator.and_) - __rand__ = __and__ - - def __or__(self, other): - return self._o2(other, operator.or_) - __ror__ = __or__ - - def __xor__(self, other): - return self._o2(other, operator.xor) - __rxor__ = __xor__ - - # Unary operations - def __neg__(self): - return Vec2d(operator.neg(self.x), operator.neg(self.y)) - - def __pos__(self): - return Vec2d(operator.pos(self.x), operator.pos(self.y)) - - def __abs__(self): - return Vec2d(abs(self.x), abs(self.y)) - - def __invert__(self): - return Vec2d(-self.x, -self.y) - - # vectory functions - def get_length_sqrd(self): - """Get the squared length of the vector. - It is more efficent to use this method instead of first call - get_length() or access .length and then do a sqrt(). - - :return: The squared length - """ - return self.x**2 + self.y**2 - - def get_length(self): - """Get the length of the vector. - - :return: The length - """ - return math.sqrt(self.x**2 + self.y**2) - def __setlength(self, value): - length = self.get_length() - self.x *= value/length - self.y *= value/length - length = property(get_length, __setlength, doc = """Gets or sets the magnitude of the vector""") - - def rotate(self, angle_degrees): - """Rotate the vector by angle_degrees degrees clockwise.""" - radians = -math.radians(angle_degrees) - cos = math.cos(radians) - sin = math.sin(radians) - x = self.x*cos - self.y*sin - y = self.x*sin + self.y*cos - self.x = x - self.y = y - - def rotated(self, angle_degrees): - """Create and return a new vector by rotating this vector by - angle_degrees degrees clockwise. - - :return: Rotated vector - """ - radians = -math.radians(angle_degrees) - cos = math.cos(radians) - sin = math.sin(radians) - x = self.x*cos - self.y*sin - y = self.x*sin + self.y*cos - return Vec2d(x, y) - - def get_angle(self): - if (self.get_length_sqrd() == 0): - return 0 - return math.degrees(math.atan2(self.y, self.x)) - def __setangle(self, angle_degrees): - self.x = self.length - self.y = 0 - self.rotate(angle_degrees) - angle = property(get_angle, __setangle, doc="""Gets or sets the angle of a vector""") - - def get_angle_between(self, other): - """Get the angle between the vector and the other in degrees - - :return: The angle - """ - cross = self.x*other[1] - self.y*other[0] - dot = self.x*other[0] + self.y*other[1] - return math.degrees(math.atan2(cross, dot)) - - def normalized(self): - """Get a normalized copy of the vector - - :return: A normalized vector - """ - length = self.length - if length != 0: - return self/length - return Vec2d(self) - - def normalize_return_length(self): - """Normalize the vector and return its length before the normalization - - :return: The length before the normalization - """ - length = self.length - if length != 0: - self.x /= length - self.y /= length - return length - - def perpendicular(self): - return Vec2d(-self.y, self.x) - - def perpendicular_normal(self): - length = self.length - if length != 0: - return Vec2d(-self.y/length, self.x/length) - return Vec2d(self) - - def dot(self, other): - """The dot product between the vector and other vector - v1.dot(v2) -> v1.x*v2.x + v1.y*v2.y - - :return: The dot product - """ - return float(self.x*other[0] + self.y*other[1]) - - def get_distance(self, other): - """The distance between the vector and other vector - - :return: The distance - """ - return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2) - - def get_dist_sqrd(self, other): - """The squared distance between the vector and other vector - It is more efficent to use this method than to call get_distance() - first and then do a sqrt() on the result. - - :return: The squared distance - """ - return (self.x - other[0])**2 + (self.y - other[1])**2 - - def projection(self, other): - other_length_sqrd = other[0]*other[0] + other[1]*other[1] - projected_length_times_other_length = self.dot(other) - return other*(projected_length_times_other_length/other_length_sqrd) - - def cross(self, other): - """The cross product between the vector and other vector - v1.cross(v2) -> v1.x*v2.y - v2.y-v1.x - - :return: The cross product - """ - return self.x*other[1] - self.y*other[0] - - def interpolate_to(self, other, range): - return Vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range) - - def convert_to_basis(self, x_vector, y_vector): - return Vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd()) - - # Extra functions, mainly for chipmunk - def cpvrotate(self, other): - return Vec2d(self.x*other.x - self.y*other.y, self.x*other.y + self.y*other.x) - def cpvunrotate(self, other): - return Vec2d(self.x*other.x + self.y*other.y, self.y*other.x - self.x*other.y) - - # Pickle, does not work atm. - def __getstate__(self): - return [self.x, self.y] - - def __setstate__(self, dict): - self.x, self.y = dict - def __newobj__(cls, *args): - return cls.__new__(cls, *args) -Vec2d._fields_ = [ - ('x', ctypes.c_double), - ('y', ctypes.c_double), - ] - - -class Vec2dArray(list): - - def __init__(self, iterable=()): - list.__init__(self, (Vec2d(i) for i in iterable)) - - def __setitem__(self, index, value): - list.__setitem__(self, index, Vec2d(value)) - - def append(self, value): - """Append a vector to the array""" - list.append(self, Vec2d(value)) - - def insert(self, index, value): - """Insert a vector into the array""" - list.insert(self, index, Vec2d(value)) - - def transform(self, offset=Vec2d(0,0), angle=0, scale=1.0): - """Return a new transformed Vec2dArray""" - offset = Vec2d(offset) - angle = math.radians(-angle) - rot_vec = Vec2d(math.cos(angle), math.sin(angle)) - xformed = Vec2dArray() - for vec in self: - xformed.append(vec.cpvrotate(rot_vec) * scale + offset) - return xformed - - def segments(self, closed=True): - """Generate arrays of line segments connecting adjacent vetices - in this array, exploding the shape into it's constituent segments - """ - if len(self) >= 2: - last = self[0] - for vert in self[1:]: - yield Vec2dArray((last, vert)) - last = vert - if closed: - yield Vec2dArray((last, self[0])) - elif self and closed: - yield Vec2dArray((self[0], self[0])) - - - -class Rect(ctypes.Structure): - """Simple rectangle. Will gain more functionality as needed""" - _fields_ = [ - ('left', ctypes.c_double), - ('top', ctypes.c_double), - ('right', ctypes.c_double), - ('bottom', ctypes.c_double), - ] - - def __init__(self, rect_or_left, bottom=None, right=None, top=None): - if bottom is not None: - assert right is not None and top is not None, "No enough arguments to Rect" - self.left = rect_or_left - self.bottom = bottom - self.right = right - self.top = top - else: - self.left = rect_or_left.left - self.bottom = rect_or_left.bottom - self.right = rect_or_left.right - self.top = rect_or_left.top - - @property - def width(self): - """Rectangle width""" - return self.right - self.left - - @property - def height(self): - """Rectangle height""" - return self.top - self.bottom - - -######################################################################## -## Unit Testing ## -######################################################################## -if __name__ == "__main__": - - import unittest - import pickle - - #################################################################### - class UnitTestVec2d(unittest.TestCase): - - def setUp(self): - pass - - def testCreationAndAccess(self): - v = Vec2d(111, 222) - self.assert_(v.x == 111 and v.y == 222) - v.x = 333 - v[1] = 444 - self.assert_(v[0] == 333 and v[1] == 444) - - def testMath(self): - v = Vec2d(111,222) - self.assertEqual(v + 1, Vec2d(112, 223)) - self.assert_(v - 2 == [109, 220]) - self.assert_(v * 3 == (333, 666)) - self.assert_(v / 2.0 == Vec2d(55.5, 111)) - #self.assert_(v / 2 == (55, 111)) # Not supported since this is a c_float structure in the bottom - self.assert_(v ** Vec2d(2, 3) == [12321, 10941048]) - self.assert_(v + [-11, 78] == Vec2d(100, 300)) - #self.assert_(v / [11,2] == [10,111]) # Not supported since this is a c_float structure in the bottom - - def testReverseMath(self): - v = Vec2d(111, 222) - self.assert_(1 + v == Vec2d(112, 223)) - self.assert_(2 - v == [-109, -220]) - self.assert_(3 * v == (333, 666)) - #self.assert_([222,999] / v == [2,4]) # Not supported since this is a c_float structure in the bottom - self.assert_([111, 222] ** Vec2d(2, 3) == [12321, 10941048]) - self.assert_([-11, 78] + v == Vec2d(100, 300)) - - def testUnary(self): - v = Vec2d(111, 222) - v = -v - self.assert_(v == [-111, -222]) - v = abs(v) - self.assert_(v == [111, 222]) - - def testLength(self): - v = Vec2d(3,4) - self.assert_(v.length == 5) - self.assert_(v.get_length_sqrd() == 25) - self.assert_(v.normalize_return_length() == 5) - self.assertAlmostEquals(v.length, 1) - v.length = 5 - self.assert_(v == Vec2d(3, 4)) - v2 = Vec2d(10, -2) - self.assert_(v.get_distance(v2) == (v - v2).get_length()) - - def testAngles(self): - v = Vec2d(0, 3) - self.assertEquals(v.angle, 90) - v2 = Vec2d(v) - v.rotate(-90) - self.assertEqual(v.get_angle_between(v2), 90) - v2.angle -= 90 - self.assertEqual(v.length, v2.length) - self.assertEquals(v2.angle, 0) - self.assertEqual(v2, [3, 0]) - self.assert_((v - v2).length < .00001) - self.assertEqual(v.length, v2.length) - v2.rotate(300) - self.assertAlmostEquals(v.get_angle_between(v2), -60, 5) # Allow a little more error than usual (floats..) - v2.rotate(v2.get_angle_between(v)) - angle = v.get_angle_between(v2) - self.assertAlmostEquals(v.get_angle_between(v2), 0) - - def testHighLevel(self): - basis0 = Vec2d(5.0, 0) - basis1 = Vec2d(0, .5) - v = Vec2d(10, 1) - self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2]) - self.assert_(v.projection(basis0) == (10, 0)) - self.assert_(basis0.dot(basis1) == 0) - - def testCross(self): - lhs = Vec2d(1, .5) - rhs = Vec2d(4, 6) - self.assert_(lhs.cross(rhs) == 4) - - def testComparison(self): - int_vec = Vec2d(3, -2) - flt_vec = Vec2d(3.0, -2.0) - zero_vec = Vec2d(0, 0) - self.assert_(int_vec == flt_vec) - self.assert_(int_vec != zero_vec) - self.assert_((flt_vec == zero_vec) == False) - self.assert_((flt_vec != int_vec) == False) - self.assert_(int_vec == (3, -2)) - self.assert_(int_vec != [0, 0]) - self.assert_(int_vec != 5) - self.assert_(int_vec != [3, -2, -5]) - - def testInplace(self): - inplace_vec = Vec2d(5, 13) - inplace_ref = inplace_vec - inplace_src = Vec2d(inplace_vec) - inplace_vec *= .5 - inplace_vec += .5 - inplace_vec /= (3, 6) - inplace_vec += Vec2d(-1, -1) - alternate = (inplace_src*.5 + .5)/Vec2d(3, 6) + [-1, -1] - self.assertEquals(inplace_vec, inplace_ref) - self.assertEquals(inplace_vec, alternate) - - def testPickle(self): - return # pickling does not work atm - testvec = Vec2d(5, .3) - testvec_str = pickle.dumps(testvec) - loaded_vec = pickle.loads(testvec_str) - self.assertEquals(testvec, loaded_vec) - - #################################################################### - unittest.main() - +__version__ = "$Id$" +__docformat__ = "reStructuredText" + +import operator +import math +import ctypes + +class Vec2d(ctypes.Structure): + """2d vector class, supports vector and scalar operators, + and also provides a bunch of high level functions + """ + __slots__ = ['x', 'y'] + + @classmethod + def from_param(cls, arg): + return cls(arg) + + def __init__(self, x_or_pair, y = None): + + if y == None: + self.x = x_or_pair[0] + self.y = x_or_pair[1] + else: + self.x = x_or_pair + self.y = y + + def __len__(self): + return 2 + + def __getitem__(self, key): + if key == 0: + return self.x + elif key == 1: + return self.y + else: + raise IndexError("Invalid subscript "+str(key)+" to Vec2d") + + def __setitem__(self, key, value): + if key == 0: + self.x = value + elif key == 1: + self.y = value + else: + raise IndexError("Invalid subscript "+str(key)+" to Vec2d") + + # String representaion (for debugging) + def __repr__(self): + return 'Vec2d(%s, %s)' % (self.x, self.y) + + # Comparison + def __eq__(self, other): + if hasattr(other, "__getitem__") and len(other) == 2: + return self.x == other[0] and self.y == other[1] + else: + return False + + def __ne__(self, other): + if hasattr(other, "__getitem__") and len(other) == 2: + return self.x != other[0] or self.y != other[1] + else: + return True + + def __nonzero__(self): + return self.x or self.y + + # Generic operator handlers + def _o2(self, other, f): + "Any two-operator operation where the left operand is a Vec2d" + if isinstance(other, Vec2d): + return Vec2d(f(self.x, other.x), + f(self.y, other.y)) + elif (hasattr(other, "__getitem__")): + return Vec2d(f(self.x, other[0]), + f(self.y, other[1])) + else: + return Vec2d(f(self.x, other), + f(self.y, other)) + + def _r_o2(self, other, f): + "Any two-operator operation where the right operand is a Vec2d" + if (hasattr(other, "__getitem__")): + return Vec2d(f(other[0], self.x), + f(other[1], self.y)) + else: + return Vec2d(f(other, self.x), + f(other, self.y)) + + def _io(self, other, f): + "inplace operator" + if (hasattr(other, "__getitem__")): + self.x = f(self.x, other[0]) + self.y = f(self.y, other[1]) + else: + self.x = f(self.x, other) + self.y = f(self.y, other) + return self + + # Addition + def __add__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x + other.x, self.y + other.y) + elif hasattr(other, "__getitem__"): + return Vec2d(self.x + other[0], self.y + other[1]) + else: + return Vec2d(self.x + other, self.y + other) + __radd__ = __add__ + + def __iadd__(self, other): + if isinstance(other, Vec2d): + self.x += other.x + self.y += other.y + elif hasattr(other, "__getitem__"): + self.x += other[0] + self.y += other[1] + else: + self.x += other + self.y += other + return self + + # Subtraction + def __sub__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x - other.x, self.y - other.y) + elif (hasattr(other, "__getitem__")): + return Vec2d(self.x - other[0], self.y - other[1]) + else: + return Vec2d(self.x - other, self.y - other) + def __rsub__(self, other): + if isinstance(other, Vec2d): + return Vec2d(other.x - self.x, other.y - self.y) + if (hasattr(other, "__getitem__")): + return Vec2d(other[0] - self.x, other[1] - self.y) + else: + return Vec2d(other - self.x, other - self.y) + def __isub__(self, other): + if isinstance(other, Vec2d): + self.x -= other.x + self.y -= other.y + elif (hasattr(other, "__getitem__")): + self.x -= other[0] + self.y -= other[1] + else: + self.x -= other + self.y -= other + return self + + # Multiplication + def __mul__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x*other.y, self.y*other.y) + if (hasattr(other, "__getitem__")): + return Vec2d(self.x*other[0], self.y*other[1]) + else: + return Vec2d(self.x*other, self.y*other) + __rmul__ = __mul__ + + def __imul__(self, other): + if isinstance(other, Vec2d): + self.x *= other.x + self.y *= other.y + elif (hasattr(other, "__getitem__")): + self.x *= other[0] + self.y *= other[1] + else: + self.x *= other + self.y *= other + return self + + # Division + def __div__(self, other): + return self._o2(other, operator.div) + def __rdiv__(self, other): + return self._r_o2(other, operator.div) + def __idiv__(self, other): + return self._io(other, operator.div) + + def __floordiv__(self, other): + return self._o2(other, operator.floordiv) + def __rfloordiv__(self, other): + return self._r_o2(other, operator.floordiv) + def __ifloordiv__(self, other): + return self._io(other, operator.floordiv) + + def __truediv__(self, other): + return self._o2(other, operator.truediv) + def __rtruediv__(self, other): + return self._r_o2(other, operator.truediv) + def __itruediv__(self, other): + return self._io(other, operator.floordiv) + + # Modulo + def __mod__(self, other): + return self._o2(other, operator.mod) + def __rmod__(self, other): + return self._r_o2(other, operator.mod) + + def __divmod__(self, other): + return self._o2(other, divmod) + def __rdivmod__(self, other): + return self._r_o2(other, divmod) + + # Exponentation + def __pow__(self, other): + return self._o2(other, operator.pow) + def __rpow__(self, other): + return self._r_o2(other, operator.pow) + + # Bitwise operators + def __lshift__(self, other): + return self._o2(other, operator.lshift) + def __rlshift__(self, other): + return self._r_o2(other, operator.lshift) + + def __rshift__(self, other): + return self._o2(other, operator.rshift) + def __rrshift__(self, other): + return self._r_o2(other, operator.rshift) + + def __and__(self, other): + return self._o2(other, operator.and_) + __rand__ = __and__ + + def __or__(self, other): + return self._o2(other, operator.or_) + __ror__ = __or__ + + def __xor__(self, other): + return self._o2(other, operator.xor) + __rxor__ = __xor__ + + # Unary operations + def __neg__(self): + return Vec2d(operator.neg(self.x), operator.neg(self.y)) + + def __pos__(self): + return Vec2d(operator.pos(self.x), operator.pos(self.y)) + + def __abs__(self): + return Vec2d(abs(self.x), abs(self.y)) + + def __invert__(self): + return Vec2d(-self.x, -self.y) + + # vectory functions + def get_length_sqrd(self): + """Get the squared length of the vector. + It is more efficent to use this method instead of first call + get_length() or access .length and then do a sqrt(). + + :return: The squared length + """ + return self.x**2 + self.y**2 + + def get_length(self): + """Get the length of the vector. + + :return: The length + """ + return math.sqrt(self.x**2 + self.y**2) + def __setlength(self, value): + length = self.get_length() + self.x *= value/length + self.y *= value/length + length = property(get_length, __setlength, doc = """Gets or sets the magnitude of the vector""") + + def rotate(self, angle_degrees): + """Rotate the vector by angle_degrees degrees clockwise.""" + radians = -math.radians(angle_degrees) + cos = math.cos(radians) + sin = math.sin(radians) + x = self.x*cos - self.y*sin + y = self.x*sin + self.y*cos + self.x = x + self.y = y + + def rotated(self, angle_degrees): + """Create and return a new vector by rotating this vector by + angle_degrees degrees clockwise. + + :return: Rotated vector + """ + radians = -math.radians(angle_degrees) + cos = math.cos(radians) + sin = math.sin(radians) + x = self.x*cos - self.y*sin + y = self.x*sin + self.y*cos + return Vec2d(x, y) + + def get_angle(self): + if (self.get_length_sqrd() == 0): + return 0 + return math.degrees(math.atan2(self.y, self.x)) + def __setangle(self, angle_degrees): + self.x = self.length + self.y = 0 + self.rotate(angle_degrees) + angle = property(get_angle, __setangle, doc="""Gets or sets the angle of a vector""") + + def get_angle_between(self, other): + """Get the angle between the vector and the other in degrees + + :return: The angle + """ + cross = self.x*other[1] - self.y*other[0] + dot = self.x*other[0] + self.y*other[1] + return math.degrees(math.atan2(cross, dot)) + + def normalized(self): + """Get a normalized copy of the vector + + :return: A normalized vector + """ + length = self.length + if length != 0: + return self/length + return Vec2d(self) + + def normalize_return_length(self): + """Normalize the vector and return its length before the normalization + + :return: The length before the normalization + """ + length = self.length + if length != 0: + self.x /= length + self.y /= length + return length + + def perpendicular(self): + return Vec2d(-self.y, self.x) + + def perpendicular_normal(self): + length = self.length + if length != 0: + return Vec2d(-self.y/length, self.x/length) + return Vec2d(self) + + def dot(self, other): + """The dot product between the vector and other vector + v1.dot(v2) -> v1.x*v2.x + v1.y*v2.y + + :return: The dot product + """ + return float(self.x*other[0] + self.y*other[1]) + + def get_distance(self, other): + """The distance between the vector and other vector + + :return: The distance + """ + return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2) + + def get_dist_sqrd(self, other): + """The squared distance between the vector and other vector + It is more efficent to use this method than to call get_distance() + first and then do a sqrt() on the result. + + :return: The squared distance + """ + return (self.x - other[0])**2 + (self.y - other[1])**2 + + def projection(self, other): + other_length_sqrd = other[0]*other[0] + other[1]*other[1] + projected_length_times_other_length = self.dot(other) + return other*(projected_length_times_other_length/other_length_sqrd) + + def cross(self, other): + """The cross product between the vector and other vector + v1.cross(v2) -> v1.x*v2.y - v2.y-v1.x + + :return: The cross product + """ + return self.x*other[1] - self.y*other[0] + + def interpolate_to(self, other, range): + return Vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range) + + def convert_to_basis(self, x_vector, y_vector): + return Vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd()) + + # Extra functions, mainly for chipmunk + def cpvrotate(self, other): + return Vec2d(self.x*other.x - self.y*other.y, self.x*other.y + self.y*other.x) + def cpvunrotate(self, other): + return Vec2d(self.x*other.x + self.y*other.y, self.y*other.x - self.x*other.y) + + # Pickle, does not work atm. + def __getstate__(self): + return [self.x, self.y] + + def __setstate__(self, dict): + self.x, self.y = dict + def __newobj__(cls, *args): + return cls.__new__(cls, *args) +Vec2d._fields_ = [ + ('x', ctypes.c_double), + ('y', ctypes.c_double), + ] + + +class Vec2dArray(list): + + def __init__(self, iterable=()): + list.__init__(self, (Vec2d(i) for i in iterable)) + + def __setitem__(self, index, value): + list.__setitem__(self, index, Vec2d(value)) + + def append(self, value): + """Append a vector to the array""" + list.append(self, Vec2d(value)) + + def insert(self, index, value): + """Insert a vector into the array""" + list.insert(self, index, Vec2d(value)) + + def transform(self, offset=Vec2d(0,0), angle=0, scale=1.0): + """Return a new transformed Vec2dArray""" + offset = Vec2d(offset) + angle = math.radians(-angle) + rot_vec = Vec2d(math.cos(angle), math.sin(angle)) + xformed = Vec2dArray() + for vec in self: + xformed.append(vec.cpvrotate(rot_vec) * scale + offset) + return xformed + + def segments(self, closed=True): + """Generate arrays of line segments connecting adjacent vetices + in this array, exploding the shape into it's constituent segments + """ + if len(self) >= 2: + last = self[0] + for vert in self[1:]: + yield Vec2dArray((last, vert)) + last = vert + if closed: + yield Vec2dArray((last, self[0])) + elif self and closed: + yield Vec2dArray((self[0], self[0])) + + + +class Rect(ctypes.Structure): + """Simple rectangle. Will gain more functionality as needed""" + _fields_ = [ + ('left', ctypes.c_double), + ('top', ctypes.c_double), + ('right', ctypes.c_double), + ('bottom', ctypes.c_double), + ] + + def __init__(self, rect_or_left, bottom=None, right=None, top=None): + if bottom is not None: + assert right is not None and top is not None, "No enough arguments to Rect" + self.left = rect_or_left + self.bottom = bottom + self.right = right + self.top = top + else: + self.left = rect_or_left.left + self.bottom = rect_or_left.bottom + self.right = rect_or_left.right + self.top = rect_or_left.top + + @property + def width(self): + """Rectangle width""" + return self.right - self.left + + @property + def height(self): + """Rectangle height""" + return self.top - self.bottom + + +######################################################################## +## Unit Testing ## +######################################################################## +if __name__ == "__main__": + + import unittest + import pickle + + #################################################################### + class UnitTestVec2d(unittest.TestCase): + + def setUp(self): + pass + + def testCreationAndAccess(self): + v = Vec2d(111, 222) + self.assert_(v.x == 111 and v.y == 222) + v.x = 333 + v[1] = 444 + self.assert_(v[0] == 333 and v[1] == 444) + + def testMath(self): + v = Vec2d(111,222) + self.assertEqual(v + 1, Vec2d(112, 223)) + self.assert_(v - 2 == [109, 220]) + self.assert_(v * 3 == (333, 666)) + self.assert_(v / 2.0 == Vec2d(55.5, 111)) + #self.assert_(v / 2 == (55, 111)) # Not supported since this is a c_float structure in the bottom + self.assert_(v ** Vec2d(2, 3) == [12321, 10941048]) + self.assert_(v + [-11, 78] == Vec2d(100, 300)) + #self.assert_(v / [11,2] == [10,111]) # Not supported since this is a c_float structure in the bottom + + def testReverseMath(self): + v = Vec2d(111, 222) + self.assert_(1 + v == Vec2d(112, 223)) + self.assert_(2 - v == [-109, -220]) + self.assert_(3 * v == (333, 666)) + #self.assert_([222,999] / v == [2,4]) # Not supported since this is a c_float structure in the bottom + self.assert_([111, 222] ** Vec2d(2, 3) == [12321, 10941048]) + self.assert_([-11, 78] + v == Vec2d(100, 300)) + + def testUnary(self): + v = Vec2d(111, 222) + v = -v + self.assert_(v == [-111, -222]) + v = abs(v) + self.assert_(v == [111, 222]) + + def testLength(self): + v = Vec2d(3,4) + self.assert_(v.length == 5) + self.assert_(v.get_length_sqrd() == 25) + self.assert_(v.normalize_return_length() == 5) + self.assertAlmostEquals(v.length, 1) + v.length = 5 + self.assert_(v == Vec2d(3, 4)) + v2 = Vec2d(10, -2) + self.assert_(v.get_distance(v2) == (v - v2).get_length()) + + def testAngles(self): + v = Vec2d(0, 3) + self.assertEquals(v.angle, 90) + v2 = Vec2d(v) + v.rotate(-90) + self.assertEqual(v.get_angle_between(v2), 90) + v2.angle -= 90 + self.assertEqual(v.length, v2.length) + self.assertEquals(v2.angle, 0) + self.assertEqual(v2, [3, 0]) + self.assert_((v - v2).length < .00001) + self.assertEqual(v.length, v2.length) + v2.rotate(300) + self.assertAlmostEquals(v.get_angle_between(v2), -60, 5) # Allow a little more error than usual (floats..) + v2.rotate(v2.get_angle_between(v)) + angle = v.get_angle_between(v2) + self.assertAlmostEquals(v.get_angle_between(v2), 0) + + def testHighLevel(self): + basis0 = Vec2d(5.0, 0) + basis1 = Vec2d(0, .5) + v = Vec2d(10, 1) + self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2]) + self.assert_(v.projection(basis0) == (10, 0)) + self.assert_(basis0.dot(basis1) == 0) + + def testCross(self): + lhs = Vec2d(1, .5) + rhs = Vec2d(4, 6) + self.assert_(lhs.cross(rhs) == 4) + + def testComparison(self): + int_vec = Vec2d(3, -2) + flt_vec = Vec2d(3.0, -2.0) + zero_vec = Vec2d(0, 0) + self.assert_(int_vec == flt_vec) + self.assert_(int_vec != zero_vec) + self.assert_((flt_vec == zero_vec) == False) + self.assert_((flt_vec != int_vec) == False) + self.assert_(int_vec == (3, -2)) + self.assert_(int_vec != [0, 0]) + self.assert_(int_vec != 5) + self.assert_(int_vec != [3, -2, -5]) + + def testInplace(self): + inplace_vec = Vec2d(5, 13) + inplace_ref = inplace_vec + inplace_src = Vec2d(inplace_vec) + inplace_vec *= .5 + inplace_vec += .5 + inplace_vec /= (3, 6) + inplace_vec += Vec2d(-1, -1) + alternate = (inplace_src*.5 + .5)/Vec2d(3, 6) + [-1, -1] + self.assertEquals(inplace_vec, inplace_ref) + self.assertEquals(inplace_vec, alternate) + + def testPickle(self): + return # pickling does not work atm + testvec = Vec2d(5, .3) + testvec_str = pickle.dumps(testvec) + loaded_vec = pickle.loads(testvec_str) + self.assertEquals(testvec, loaded_vec) + + #################################################################### + unittest.main() + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/impl/__init__.py --- a/bGrease/impl/__init__.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/impl/__init__.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,22 +1,22 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# 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. -# -############################################################################# - -__versioninfo__ = (0, 3, 0) -__version__ = '.'.join(str(n) for n in __versioninfo__) - -__all__ = ('Mode', 'World') - -from mode import Mode -from world import World - - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# 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. +# +############################################################################# + +__versioninfo__ = (0, 3, 0) +__version__ = '.'.join(str(n) for n in __versioninfo__) + +__all__ = ('Mode', 'World') + +from mode import Mode +from world import World + + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/impl/controls.py --- a/bGrease/impl/controls.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/impl/controls.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,216 +1,216 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# 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. -# -############################################################################# -"""Control systems for binding controls to game logic""" - -import bGrease -from pyglet.window import key - -class KeyControls(grease.System): - """System that maps subclass-defined action methods to keys. - - Keys may be mapped in the subclass definition using decorators - defined here as class methods or at runtime using the ``bind_key_*`` - instance methods. - - See :ref:`an example implementation in the tutorial `. - """ - MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CAPSLOCK) - """The MODIFIER_MASK allows you to filter out modifier keys that should be - ignored by the application. By default, capslock, numlock, and scrolllock - are ignored. - """ - - world = None - """:class:`grease.World` object this system is bound to""" - - def __init__(self): - self._key_press_map = {} - self._key_release_map = {} - self._key_hold_map = {} - for name in self.__class__.__dict__: - member = getattr(self, name) - if hasattr(member, '_grease_hold_key_binding'): - for binding in member._grease_hold_key_binding: - self.bind_key_hold(member, *binding) - if hasattr(member, '_grease_press_key_binding'): - for binding in member._grease_press_key_binding: - self.bind_key_press(member, *binding) - if hasattr(member, '_grease_release_key_binding'): - for binding in member._grease_release_key_binding: - self.bind_key_release(member, *binding) - self.held_keys = set() - - ## decorator methods for binding methods to key input events ## - - @classmethod - def key_hold(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is held down""" - def bind(f): - if not hasattr(f, '_grease_hold_key_binding'): - f._grease_hold_key_binding = [] - f._grease_hold_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - @classmethod - def key_press(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is initially depressed""" - def bind(f): - if not hasattr(f, '_grease_press_key_binding'): - f._grease_press_key_binding = [] - f._grease_press_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - @classmethod - def key_release(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is released""" - def bind(f): - if not hasattr(f, '_grease_release_key_binding'): - f._grease_release_key_binding = [] - f._grease_release_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - ## runtime binding methods ## - - def bind_key_hold(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is - held down, this replaces any existing key hold binding for this key. - To unbind the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_hold_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_hold_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def bind_key_press(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is initially - pressed, this replaces any existing key hold binding for this key. To unbind - the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_press_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_press_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def bind_key_release(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is releaseed, - this replaces any existing key hold binding for this key. To unbind - the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_release_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_release_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def step(self, dt): - """invoke held key functions""" - already_run = set() - for key in self.held_keys: - func = self._key_hold_map.get(key) - if func is not None and func not in already_run: - already_run.add(func) - func(dt) - - def on_key_press(self, key, modifiers): - """Handle pyglet key press. Invoke key press methods and - activate key hold functions - """ - key_mod = (key, modifiers & self.MODIFIER_MASK) - if key_mod in self._key_press_map: - self._key_press_map[key_mod]() - self.held_keys.add(key_mod) - - def on_key_release(self, key, modifiers): - """Handle pyglet key release. Invoke key release methods and - deactivate key hold functions - """ - key_mod = (key, modifiers & self.MODIFIER_MASK) - if key_mod in self._key_release_map: - self._key_release_map[key_mod]() - self.held_keys.discard(key_mod) - - -if __name__ == '__main__': - import pyglet - - class TestKeyControls(KeyControls): - - MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CTRL) - - remapped = False - - @KeyControls.key_hold(key.UP) - @KeyControls.key_hold(key.W) - def up(self, dt): - print 'UP!' - - @KeyControls.key_hold(key.LEFT) - @KeyControls.key_hold(key.A) - def left(self, dt): - print 'LEFT!' - - @KeyControls.key_hold(key.RIGHT) - @KeyControls.key_hold(key.D) - def right(self, dt): - print 'RIGHT!' - - @KeyControls.key_hold(key.DOWN) - @KeyControls.key_hold(key.S) - def down(self, dt): - print 'DOWN!' - - @KeyControls.key_press(key.SPACE) - def fire(self): - print 'FIRE!' - - @KeyControls.key_press(key.R) - def remap_keys(self): - if not self.remapped: - self.bind_key_hold(None, key.W) - self.bind_key_hold(None, key.A) - self.bind_key_hold(None, key.S) - self.bind_key_hold(None, key.D) - self.bind_key_hold(self.up, key.I) - self.bind_key_hold(self.left, key.J) - self.bind_key_hold(self.right, key.L) - self.bind_key_hold(self.down, key.K) - else: - self.bind_key_hold(None, key.I) - self.bind_key_hold(None, key.J) - self.bind_key_hold(None, key.K) - self.bind_key_hold(None, key.L) - self.bind_key_hold(self.up, key.W) - self.bind_key_hold(self.left, key.A) - self.bind_key_hold(self.right, key.D) - self.bind_key_hold(self.down, key.S) - self.remapped = not self.remapped - - - window = pyglet.window.Window() - window.clear() - controls = TestKeyControls() - window.push_handlers(controls) - pyglet.clock.schedule_interval(controls.step, 0.5) - pyglet.app.run() - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# 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. +# +############################################################################# +"""Control systems for binding controls to game logic""" + +import bGrease +from pyglet.window import key + +class KeyControls(grease.System): + """System that maps subclass-defined action methods to keys. + + Keys may be mapped in the subclass definition using decorators + defined here as class methods or at runtime using the ``bind_key_*`` + instance methods. + + See :ref:`an example implementation in the tutorial `. + """ + MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CAPSLOCK) + """The MODIFIER_MASK allows you to filter out modifier keys that should be + ignored by the application. By default, capslock, numlock, and scrolllock + are ignored. + """ + + world = None + """:class:`grease.World` object this system is bound to""" + + def __init__(self): + self._key_press_map = {} + self._key_release_map = {} + self._key_hold_map = {} + for name in self.__class__.__dict__: + member = getattr(self, name) + if hasattr(member, '_grease_hold_key_binding'): + for binding in member._grease_hold_key_binding: + self.bind_key_hold(member, *binding) + if hasattr(member, '_grease_press_key_binding'): + for binding in member._grease_press_key_binding: + self.bind_key_press(member, *binding) + if hasattr(member, '_grease_release_key_binding'): + for binding in member._grease_release_key_binding: + self.bind_key_release(member, *binding) + self.held_keys = set() + + ## decorator methods for binding methods to key input events ## + + @classmethod + def key_hold(cls, symbol, modifiers=0): + """Decorator to bind a method to be executed where a key is held down""" + def bind(f): + if not hasattr(f, '_grease_hold_key_binding'): + f._grease_hold_key_binding = [] + f._grease_hold_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) + return f + return bind + + @classmethod + def key_press(cls, symbol, modifiers=0): + """Decorator to bind a method to be executed where a key is initially depressed""" + def bind(f): + if not hasattr(f, '_grease_press_key_binding'): + f._grease_press_key_binding = [] + f._grease_press_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) + return f + return bind + + @classmethod + def key_release(cls, symbol, modifiers=0): + """Decorator to bind a method to be executed where a key is released""" + def bind(f): + if not hasattr(f, '_grease_release_key_binding'): + f._grease_release_key_binding = [] + f._grease_release_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) + return f + return bind + + ## runtime binding methods ## + + def bind_key_hold(self, method, key, modifiers=0): + """Bind a method to a key at runtime to be invoked when the key is + held down, this replaces any existing key hold binding for this key. + To unbind the key entirely, pass ``None`` for method. + """ + if method is not None: + self._key_hold_map[key, modifiers & self.MODIFIER_MASK] = method + else: + try: + del self._key_hold_map[key, modifiers & self.MODIFIER_MASK] + except KeyError: + pass + + def bind_key_press(self, method, key, modifiers=0): + """Bind a method to a key at runtime to be invoked when the key is initially + pressed, this replaces any existing key hold binding for this key. To unbind + the key entirely, pass ``None`` for method. + """ + if method is not None: + self._key_press_map[key, modifiers & self.MODIFIER_MASK] = method + else: + try: + del self._key_press_map[key, modifiers & self.MODIFIER_MASK] + except KeyError: + pass + + def bind_key_release(self, method, key, modifiers=0): + """Bind a method to a key at runtime to be invoked when the key is releaseed, + this replaces any existing key hold binding for this key. To unbind + the key entirely, pass ``None`` for method. + """ + if method is not None: + self._key_release_map[key, modifiers & self.MODIFIER_MASK] = method + else: + try: + del self._key_release_map[key, modifiers & self.MODIFIER_MASK] + except KeyError: + pass + + def step(self, dt): + """invoke held key functions""" + already_run = set() + for key in self.held_keys: + func = self._key_hold_map.get(key) + if func is not None and func not in already_run: + already_run.add(func) + func(dt) + + def on_key_press(self, key, modifiers): + """Handle pyglet key press. Invoke key press methods and + activate key hold functions + """ + key_mod = (key, modifiers & self.MODIFIER_MASK) + if key_mod in self._key_press_map: + self._key_press_map[key_mod]() + self.held_keys.add(key_mod) + + def on_key_release(self, key, modifiers): + """Handle pyglet key release. Invoke key release methods and + deactivate key hold functions + """ + key_mod = (key, modifiers & self.MODIFIER_MASK) + if key_mod in self._key_release_map: + self._key_release_map[key_mod]() + self.held_keys.discard(key_mod) + + +if __name__ == '__main__': + import pyglet + + class TestKeyControls(KeyControls): + + MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CTRL) + + remapped = False + + @KeyControls.key_hold(key.UP) + @KeyControls.key_hold(key.W) + def up(self, dt): + print 'UP!' + + @KeyControls.key_hold(key.LEFT) + @KeyControls.key_hold(key.A) + def left(self, dt): + print 'LEFT!' + + @KeyControls.key_hold(key.RIGHT) + @KeyControls.key_hold(key.D) + def right(self, dt): + print 'RIGHT!' + + @KeyControls.key_hold(key.DOWN) + @KeyControls.key_hold(key.S) + def down(self, dt): + print 'DOWN!' + + @KeyControls.key_press(key.SPACE) + def fire(self): + print 'FIRE!' + + @KeyControls.key_press(key.R) + def remap_keys(self): + if not self.remapped: + self.bind_key_hold(None, key.W) + self.bind_key_hold(None, key.A) + self.bind_key_hold(None, key.S) + self.bind_key_hold(None, key.D) + self.bind_key_hold(self.up, key.I) + self.bind_key_hold(self.left, key.J) + self.bind_key_hold(self.right, key.L) + self.bind_key_hold(self.down, key.K) + else: + self.bind_key_hold(None, key.I) + self.bind_key_hold(None, key.J) + self.bind_key_hold(None, key.K) + self.bind_key_hold(None, key.L) + self.bind_key_hold(self.up, key.W) + self.bind_key_hold(self.left, key.A) + self.bind_key_hold(self.right, key.D) + self.bind_key_hold(self.down, key.S) + self.remapped = not self.remapped + + + window = pyglet.window.Window() + window.clear() + controls = TestKeyControls() + window.push_handlers(controls) + pyglet.clock.schedule_interval(controls.step, 0.5) + pyglet.app.run() + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/mode.py --- a/bGrease/mode.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/mode.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,391 +1,391 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# 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. -# -############################################################################# -""" -Modes manage the state and transition between different application modes. -Typically such modes are presented as different screens that the user can -navigate between, similar to the way a browser navigates web pages. Individual -modes may be things like: - -- Title screen -- Options dialog -- About screen -- In-progress game -- Inventory interface - -The modal framework provides a simple mechanism to ensure that modes are -activated and deactivated properly. An activated mode is running and receives -events. A deactivated mode is paused and does not receive events. - -Modes may be managed as a *last-in-first-out* stack, or as a list, or ring -of modes in sequence, or some combination of all. - -For example usage see: :ref:`the mode section of the tutorial `. -""" - -__version__ = '$Id$' - -import abc - - -class BaseManager(object): - """Mode manager abstract base class. - - The mode manager keeps a stack of modes where a single mode - is active at one time. As modes are pushed on and popped from - the stack, the mode at the top is always active. The current - active mode receives events from the manager's event dispatcher. - """ - - modes = () - """The mode stack sequence. The last mode in the stack is - the current active mode. Read-only. - """ - - @property - def current_mode(self): - """The current active mode or ``None``. Read-only""" - try: - return self.modes[-1] - except IndexError: - return None - - def on_last_mode_pop(self, mode): - """Hook executed when the last mode is popped from the manager. - Implementing this method is optional for subclasses. - - :param mode: The :class:`Mode` object just popped from the manager - """ - - def activate_mode(self, mode): - """Perform actions to activate a node - - :param mode: The :class: 'Mode' object to activate - """ - mode.activate(self) - - def deactivate_mode(self, mode): - """Perform actions to deactivate a node - - :param mode: The :class: 'Mode' object to deactivate - """ - mode.deactivate(self) - - - def push_mode(self, mode): - """Push a mode to the top of the mode stack and make it active - - :param mode: The :class:`Mode` object to make active - """ - current = self.current_mode - if current is not None: - self.deactivate_mode(current) - self.modes.append(mode) - self.activate_mode(mode) - - def pop_mode(self): - """Pop the current mode off the top of the stack and deactivate it. - The mode now at the top of the stack, if any is then activated. - - :param mode: The :class:`Mode` object popped from the stack - """ - mode = self.modes.pop() - mode.deactivate(self) - current = self.current_mode - if current is not None: - self.activate_mode(current) - else: - self.on_last_mode_pop(mode) - return mode - - def swap_modes(self, mode): - """Exchange the specified mode with the mode at the top of the stack. - This is similar to popping the current mode and pushing the specified - one, but without activating the previous mode on the stack or - executing :meth:`on_last_mode_pop()` if there is no previous mode. - - :param mode: The :class:`Mode` object that was deactivated and replaced. - """ - old_mode = self.modes.pop() - self.deactivate_mode(old_mode) - self.modes.append(mode) - self.activate_mode(mode) - return old_mode - - def remove_mode(self, mode): - """Remove the specified mode. If the mode is at the top of the stack, - this is equivilent to :meth:`pop_mode()`. If not, no other modes - are affected. If the mode is not in the manager, do nothing. - - :param mode: The :class:`Mode` object to remove from the manager. - """ - if self.current_mode is mode: - self.pop_mode() - else: - try: - self.modes.remove(mode) - except ValueError: - pass - -class BaseMode(object): - """Application mode very abstract base class - """ - __metaclass__ = abc.ABCMeta - - manager = None - """The :class:`BaseManager` that manages this mode""" - - def __init__(self): - self.active = False - - def on_activate(self): - """Being called when the Mode is activated""" - pass - - def activate(self, mode_manager): - """Activate the mode for the given mode manager, if the mode is already active, - do nothing - - The default implementation schedules time steps at :attr:`step_rate` per - second, sets the :attr:`manager` and sets the :attr:`active` flag to True. - """ - if not self.active: - self.on_activate() - self.manager = mode_manager - self.active = True - - def on_deactivate(self): - """Being called when the Mode is deactivated""" - pass - - def deactivate(self, mode_manager): - """Deactivate the mode, if the mode is not active, do nothing - - The default implementation unschedules time steps for the mode and - sets the :attr:`active` flag to False. - """ - self.on_deactivate() - self.active = False - - -class BaseMulti(BaseMode): - """A mode with multiple submodes. One submode is active at one time. - Submodes can be switched to directly or switched in sequence. If - the Multi is active, then one submode is always active. - - Multis are useful when modes can switch in an order other than - a LIFO stack, such as in "hotseat" multiplayer games, a - "wizard" style ui, or a sequence of slides. - - Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own - :attr:`clock` and :attr:`step_rate`. The active submode's are used - instead. - """ - active_submode = None - """The currently active submode""" - - def __init__(self, *submodes): - # We do not invoke the superclass __init__ intentionally - self.active = False - self.submodes = list(submodes) - - def add_submode(self, mode, before=None, index=None): - """Add the submode, but do not make it active. - - :param mode: The :class:`Mode` object to add. - - :param before: The existing mode to insert the mode before. - If the mode specified is not a submode, raise - ValueError. - - :param index: The place to insert the mode in the mode list. - Only one of ``before`` or ``index`` may be specified. - - If neither ``before`` or ``index`` are specified, the - mode is appended to the end of the list. - """ - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments") - if before is not None: - index = self.submodes.index(mode) - if index is not None: - self.submodes.insert(index, mode) - else: - self.submodes.append(mode) - - def remove_submode(self, mode=None): - """Remove the submode. - - :param mode: The submode to remove, if omitted the active submode - is removed. If the mode is not present, do nothing. If the - mode is active, it is deactivated, and the next mode, if any - is activated. If the last mode is removed, the :class:`Multi` - is removed from its manager. - """ - # TODO handle multiple instances of the same subnode - if mode is None: - mode = self.active_submode - elif mode not in self.submodes: - return - next_mode = self.activate_next() - self.submodes.remove(mode) - if next_mode is mode: - if self.manager is not None: - self.manager.remove_mode(self) - self._deactivate_submode() - - def activate_subnode(self, mode, before=None, index=None): - """Activate the specified mode, adding it as a subnode - if it is not already. If the mode is already the active - submode, do nothing. - - :param mode: The mode to activate, and add as necesary. - - :param before: The existing mode to insert the mode before - if it is not already a submode. If the mode specified is not - a submode, raise ValueError. - - :param index: The place to insert the mode in the mode list - if it is not already a submode. Only one of ``before`` or - ``index`` may be specified. - - If the mode is already a submode, the ``before`` and ``index`` - arguments are ignored. - """ - if mode not in self.submodes: - self.add_submode(mode, before, index) - if self.active_submode is not mode: - self._activate_submode(mode) - - def activate_next(self, loop=True): - """Activate the submode after the current submode in order. If there - is no current submode, the first submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_next` is called - when the last submode is active, a True value for ``loop`` will - cause the first submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - next_mode = None - if self.active_submode is None: - next_mode = self.submodes[0] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) + 1 - if index < len(self.submodes): - next_mode = self.submodes[index] - elif loop: - next_mode = self.submodes[0] - self._activate_submode(next_mode) - return next_mode - - def activate_previous(self, loop=True): - """Activate the submode before the current submode in order. If there - is no current submode, the last submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_previous` is called - when the first submode is active, a True value for ``loop`` will - cause the last submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - prev_mode = None - if self.active_submode is None: - prev_mode = self.submodes[-1] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) - 1 - if loop or index >= 0: - prev_mode = self.submodes[index] - self._activate_submode(prev_mode) - return prev_mode - - def _set_active_submode(self, submode): - self.active_submode = submode - self.step_rate = submode.step_rate - - def _activate_submode(self, submode): - """Activate a submode deactivating any current submode. If the Multi - itself is active, this happens immediately, otherwise the actual - activation is deferred until the Multi is activated. If the submode - is None, the Mulitmode is removed from its manager. - - If submode is already the active submode, do nothing. - """ - if self.active_submode is submode: - return - assert submode in self.submodes, "Unknown submode" - self._deactivate_submode() - self._set_active_submode(submode) - if submode is not None: - if self.active: - self.manager.activate_mode(submode) - else: - if self.manager is not None: - self.manager.remove_mode(self) - - def clear_subnode(self): - """Clear any subnmode data""" - self.active_submode = None - self.step_rate = None - - def _deactivate_submode(self, clear_subnode=True): - """Deactivate the current submode, if any. if `clear_subnode` is - True, `active_submode` is always None when this method returns - """ - if self.active_submode is not None: - if self.active: - self.manager.deactivate_mode(self.active_submode) - if clear_subnode: - self.clear_subnode() - - def activate(self, mode_manager): - """Activate the :class:`Multi` for the specified manager. The - previously active submode of the :class:`Multi` is activated. If there - is no previously active submode, then the first submode is made active. - A :class:`Multi` with no submodes cannot be activated - """ - assert self.submodes, "No submode to activate" - self.manager = mode_manager - if self.active_submode is None: - self._set_active_submode(self.submodes[0]) - else: - self._set_active_submode(self.active_submode) - self.manager.activate_mode(self.active_submode) - super(BaseMulti, self).activate(mode_manager) - - def deactivate(self, mode_manager): - """Deactivate the :class:`Multi` for the specified manager. - The `active_submode`, if any, is deactivated. - """ - self._deactivate_submode(clear_subnode=False) - super(BaseMulti, self).deactivate(mode_manager) - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# 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. +# +############################################################################# +""" +Modes manage the state and transition between different application modes. +Typically such modes are presented as different screens that the user can +navigate between, similar to the way a browser navigates web pages. Individual +modes may be things like: + +- Title screen +- Options dialog +- About screen +- In-progress game +- Inventory interface + +The modal framework provides a simple mechanism to ensure that modes are +activated and deactivated properly. An activated mode is running and receives +events. A deactivated mode is paused and does not receive events. + +Modes may be managed as a *last-in-first-out* stack, or as a list, or ring +of modes in sequence, or some combination of all. + +For example usage see: :ref:`the mode section of the tutorial `. +""" + +__version__ = '$Id$' + +import abc + + +class BaseManager(object): + """Mode manager abstract base class. + + The mode manager keeps a stack of modes where a single mode + is active at one time. As modes are pushed on and popped from + the stack, the mode at the top is always active. The current + active mode receives events from the manager's event dispatcher. + """ + + modes = () + """The mode stack sequence. The last mode in the stack is + the current active mode. Read-only. + """ + + @property + def current_mode(self): + """The current active mode or ``None``. Read-only""" + try: + return self.modes[-1] + except IndexError: + return None + + def on_last_mode_pop(self, mode): + """Hook executed when the last mode is popped from the manager. + Implementing this method is optional for subclasses. + + :param mode: The :class:`Mode` object just popped from the manager + """ + + def activate_mode(self, mode): + """Perform actions to activate a node + + :param mode: The :class: 'Mode' object to activate + """ + mode.activate(self) + + def deactivate_mode(self, mode): + """Perform actions to deactivate a node + + :param mode: The :class: 'Mode' object to deactivate + """ + mode.deactivate(self) + + + def push_mode(self, mode): + """Push a mode to the top of the mode stack and make it active + + :param mode: The :class:`Mode` object to make active + """ + current = self.current_mode + if current is not None: + self.deactivate_mode(current) + self.modes.append(mode) + self.activate_mode(mode) + + def pop_mode(self): + """Pop the current mode off the top of the stack and deactivate it. + The mode now at the top of the stack, if any is then activated. + + :param mode: The :class:`Mode` object popped from the stack + """ + mode = self.modes.pop() + mode.deactivate(self) + current = self.current_mode + if current is not None: + self.activate_mode(current) + else: + self.on_last_mode_pop(mode) + return mode + + def swap_modes(self, mode): + """Exchange the specified mode with the mode at the top of the stack. + This is similar to popping the current mode and pushing the specified + one, but without activating the previous mode on the stack or + executing :meth:`on_last_mode_pop()` if there is no previous mode. + + :param mode: The :class:`Mode` object that was deactivated and replaced. + """ + old_mode = self.modes.pop() + self.deactivate_mode(old_mode) + self.modes.append(mode) + self.activate_mode(mode) + return old_mode + + def remove_mode(self, mode): + """Remove the specified mode. If the mode is at the top of the stack, + this is equivilent to :meth:`pop_mode()`. If not, no other modes + are affected. If the mode is not in the manager, do nothing. + + :param mode: The :class:`Mode` object to remove from the manager. + """ + if self.current_mode is mode: + self.pop_mode() + else: + try: + self.modes.remove(mode) + except ValueError: + pass + +class BaseMode(object): + """Application mode very abstract base class + """ + __metaclass__ = abc.ABCMeta + + manager = None + """The :class:`BaseManager` that manages this mode""" + + def __init__(self): + self.active = False + + def on_activate(self): + """Being called when the Mode is activated""" + pass + + def activate(self, mode_manager): + """Activate the mode for the given mode manager, if the mode is already active, + do nothing + + The default implementation schedules time steps at :attr:`step_rate` per + second, sets the :attr:`manager` and sets the :attr:`active` flag to True. + """ + if not self.active: + self.on_activate() + self.manager = mode_manager + self.active = True + + def on_deactivate(self): + """Being called when the Mode is deactivated""" + pass + + def deactivate(self, mode_manager): + """Deactivate the mode, if the mode is not active, do nothing + + The default implementation unschedules time steps for the mode and + sets the :attr:`active` flag to False. + """ + self.on_deactivate() + self.active = False + + +class BaseMulti(BaseMode): + """A mode with multiple submodes. One submode is active at one time. + Submodes can be switched to directly or switched in sequence. If + the Multi is active, then one submode is always active. + + Multis are useful when modes can switch in an order other than + a LIFO stack, such as in "hotseat" multiplayer games, a + "wizard" style ui, or a sequence of slides. + + Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own + :attr:`clock` and :attr:`step_rate`. The active submode's are used + instead. + """ + active_submode = None + """The currently active submode""" + + def __init__(self, *submodes): + # We do not invoke the superclass __init__ intentionally + self.active = False + self.submodes = list(submodes) + + def add_submode(self, mode, before=None, index=None): + """Add the submode, but do not make it active. + + :param mode: The :class:`Mode` object to add. + + :param before: The existing mode to insert the mode before. + If the mode specified is not a submode, raise + ValueError. + + :param index: The place to insert the mode in the mode list. + Only one of ``before`` or ``index`` may be specified. + + If neither ``before`` or ``index`` are specified, the + mode is appended to the end of the list. + """ + assert before is None or index is None, ( + "Cannot specify both 'before' and 'index' arguments") + if before is not None: + index = self.submodes.index(mode) + if index is not None: + self.submodes.insert(index, mode) + else: + self.submodes.append(mode) + + def remove_submode(self, mode=None): + """Remove the submode. + + :param mode: The submode to remove, if omitted the active submode + is removed. If the mode is not present, do nothing. If the + mode is active, it is deactivated, and the next mode, if any + is activated. If the last mode is removed, the :class:`Multi` + is removed from its manager. + """ + # TODO handle multiple instances of the same subnode + if mode is None: + mode = self.active_submode + elif mode not in self.submodes: + return + next_mode = self.activate_next() + self.submodes.remove(mode) + if next_mode is mode: + if self.manager is not None: + self.manager.remove_mode(self) + self._deactivate_submode() + + def activate_subnode(self, mode, before=None, index=None): + """Activate the specified mode, adding it as a subnode + if it is not already. If the mode is already the active + submode, do nothing. + + :param mode: The mode to activate, and add as necesary. + + :param before: The existing mode to insert the mode before + if it is not already a submode. If the mode specified is not + a submode, raise ValueError. + + :param index: The place to insert the mode in the mode list + if it is not already a submode. Only one of ``before`` or + ``index`` may be specified. + + If the mode is already a submode, the ``before`` and ``index`` + arguments are ignored. + """ + if mode not in self.submodes: + self.add_submode(mode, before, index) + if self.active_submode is not mode: + self._activate_submode(mode) + + def activate_next(self, loop=True): + """Activate the submode after the current submode in order. If there + is no current submode, the first submode is activated. + + Note if there is only one submode, it's active, and `loop` is True + (the default), then this method does nothing and the subnode remains + active. + + :param loop: When :meth:`activate_next` is called + when the last submode is active, a True value for ``loop`` will + cause the first submode to be activated. Otherwise the + :class:`Multi` is removed from its manager. + :type loop: bool + + :return: + The submode that was activated or None if there is no + other submode to activate. + """ + assert self.submodes, "No submode to activate" + next_mode = None + if self.active_submode is None: + next_mode = self.submodes[0] + else: + last_mode = self.active_submode + index = self.submodes.index(last_mode) + 1 + if index < len(self.submodes): + next_mode = self.submodes[index] + elif loop: + next_mode = self.submodes[0] + self._activate_submode(next_mode) + return next_mode + + def activate_previous(self, loop=True): + """Activate the submode before the current submode in order. If there + is no current submode, the last submode is activated. + + Note if there is only one submode, it's active, and `loop` is True + (the default), then this method does nothing and the subnode remains + active. + + :param loop: When :meth:`activate_previous` is called + when the first submode is active, a True value for ``loop`` will + cause the last submode to be activated. Otherwise the + :class:`Multi` is removed from its manager. + :type loop: bool + + :return: + The submode that was activated or None if there is no + other submode to activate. + """ + assert self.submodes, "No submode to activate" + prev_mode = None + if self.active_submode is None: + prev_mode = self.submodes[-1] + else: + last_mode = self.active_submode + index = self.submodes.index(last_mode) - 1 + if loop or index >= 0: + prev_mode = self.submodes[index] + self._activate_submode(prev_mode) + return prev_mode + + def _set_active_submode(self, submode): + self.active_submode = submode + self.step_rate = submode.step_rate + + def _activate_submode(self, submode): + """Activate a submode deactivating any current submode. If the Multi + itself is active, this happens immediately, otherwise the actual + activation is deferred until the Multi is activated. If the submode + is None, the Mulitmode is removed from its manager. + + If submode is already the active submode, do nothing. + """ + if self.active_submode is submode: + return + assert submode in self.submodes, "Unknown submode" + self._deactivate_submode() + self._set_active_submode(submode) + if submode is not None: + if self.active: + self.manager.activate_mode(submode) + else: + if self.manager is not None: + self.manager.remove_mode(self) + + def clear_subnode(self): + """Clear any subnmode data""" + self.active_submode = None + self.step_rate = None + + def _deactivate_submode(self, clear_subnode=True): + """Deactivate the current submode, if any. if `clear_subnode` is + True, `active_submode` is always None when this method returns + """ + if self.active_submode is not None: + if self.active: + self.manager.deactivate_mode(self.active_submode) + if clear_subnode: + self.clear_subnode() + + def activate(self, mode_manager): + """Activate the :class:`Multi` for the specified manager. The + previously active submode of the :class:`Multi` is activated. If there + is no previously active submode, then the first submode is made active. + A :class:`Multi` with no submodes cannot be activated + """ + assert self.submodes, "No submode to activate" + self.manager = mode_manager + if self.active_submode is None: + self._set_active_submode(self.submodes[0]) + else: + self._set_active_submode(self.active_submode) + self.manager.activate_mode(self.active_submode) + super(BaseMulti, self).activate(mode_manager) + + def deactivate(self, mode_manager): + """Deactivate the :class:`Multi` for the specified manager. + The `active_submode`, if any, is deactivated. + """ + self._deactivate_submode(clear_subnode=False) + super(BaseMulti, self).deactivate(mode_manager) + diff -r 95461b06bac1 -r a6bbb732b27b bGrease/renderer/camera.py --- a/bGrease/renderer/camera.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/renderer/camera.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,39 +1,39 @@ -import pyglet - -class Camera(object): - """Sets the point of view for further renderers by altering the - model/view matrix when it is drawn. It does not actually perform - any drawing itself. - - :param position: The position vector for the camera. Sets the center of the view. - :type position: Vec2d - :param angle: Camera rotation in degrees about the z-axis. - :type angle: float - :param zoom: Scaling vector for the coordinate axis. - :type zoom: Vec2d - :param relative: Flag to indicate if the camera settings are relative - to the previous view state. If ``False`` the view state is reset before - setting the camera view by loading the identity model/view matrix. - - At runtime the camera may be manipulated via attributes with the - same names and functions as the parameters above. - """ - - def __init__(self, position=None, angle=None, zoom=None, relative=False): - self.position = position - self.angle = angle - self.zoom = zoom - self.relative = relative - - def draw(self, gl=pyglet.gl): - if not self.relative: - gl.glLoadIdentity() - if self.position is not None: - px, py = self.position - gl.glTranslatef(px, py, 0) - if self.angle is not None: - gl.glRotatef(self.angle, 0, 0, 1) - if self.zoom is not None: - sx, sy = self.zoom - gl.glScalef(sx, sy ,0) - +import pyglet + +class Camera(object): + """Sets the point of view for further renderers by altering the + model/view matrix when it is drawn. It does not actually perform + any drawing itself. + + :param position: The position vector for the camera. Sets the center of the view. + :type position: Vec2d + :param angle: Camera rotation in degrees about the z-axis. + :type angle: float + :param zoom: Scaling vector for the coordinate axis. + :type zoom: Vec2d + :param relative: Flag to indicate if the camera settings are relative + to the previous view state. If ``False`` the view state is reset before + setting the camera view by loading the identity model/view matrix. + + At runtime the camera may be manipulated via attributes with the + same names and functions as the parameters above. + """ + + def __init__(self, position=None, angle=None, zoom=None, relative=False): + self.position = position + self.angle = angle + self.zoom = zoom + self.relative = relative + + def draw(self, gl=pyglet.gl): + if not self.relative: + gl.glLoadIdentity() + if self.position is not None: + px, py = self.position + gl.glTranslatef(px, py, 0) + if self.angle is not None: + gl.glRotatef(self.angle, 0, 0, 1) + if self.zoom is not None: + sx, sy = self.zoom + gl.glScalef(sx, sy ,0) +