# HG changeset patch # User Beliar # Date 1332848480 -7200 # Node ID 2a12e2843984bae6b3b885b67cb22b87e5366701 # Parent ab6a0fd1668aa7a294682ed31d083682efeb626d Removed bGrease, which is in a seperate repository, here: https://github.com/Beliaar/bGrease diff -r ab6a0fd1668a -r 2a12e2843984 application.py --- a/application.py Sat Mar 24 09:59:46 2012 +0100 +++ b/application.py Tue Mar 27 13:41:20 2012 +0200 @@ -31,7 +31,7 @@ from parpg.common.listeners.command_listener import CommandListener from parpg.common.listeners.console_executor import ConsoleExecuter from parpg.common.listeners.widget_listener import WidgetListener -from parpg.mode import FifeManager +from bGrease.grease_fife.mode import FifeManager class KeyFilter(fife.IKeyFilter): """ diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/__init__.py --- a/bGrease/__init__.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -############################################################################# -# -# 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 ab6a0fd1668a -r 2a12e2843984 bGrease/collision.py --- a/bGrease/collision.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,527 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -""" -**Grease collision detection systems** - -Grease uses two-phase broad and narrow collision detection. *Broad-phase* -collision systems are used to efficiently identify pairs that may be colliding -without resorting to a brute-force check of all possible pairs. *Narrow-phase* -collision systems use the pairs generated by the broad-phase and perform more -precise collision tests to determine if a collision has actually occurred. The -narrow-phase system also calculates more details about each collision, -including collision point and normal vector for use in collision response. - -A typical collision detection system consists of a narrow-phase system that -contains a broad-phased system. The narrow-phase system is usually the only - -one that the application directly interacts with, though the application is -free to use the broad-phased system directly if desired. This could be -useful in cases where speed, rather than precision is paramount. - -The narrow-phase system can be assigned handler objects to run after -collision detection. These can perform tasks like handling collision response -or dispatching collision events to application handlers. - -Note that broad-phase systems can return false positives, though they should -never return false negatives. Do not assume that all pairs returned by a -broad-phase system are actually in collision. -""" - -__version__ = '$Id$' - -from parpg.bGrease.geometry import Vec2d -from bisect import bisect_right - - -class Pair(tuple): - """Pair of entities in collision. This is an ordered sequence of two - entities, that compares and hashes unordered. - - Also stores additional collision point and normal vectors - for each entity. - - Sets of ``Pair`` objects are exposed in the ``collision_pairs`` - attribute of collision systems to indicate the entity pairs in - collision. - """ - info = None - """A sequence of (entity, collision point, collision normal) - for each entity in the pair - """ - - def __new__(cls, entity1, entity2, point=None, normal=None): - pair = tuple.__new__(cls, (entity1, entity2)) - return pair - - def __hash__(self): - return hash(self[0]) ^ hash(self[1]) - - def __eq__(self, other): - other = tuple(other) - return tuple(self) == other or (self[1], self[0]) == other - - def __repr__(self): - return '%s%r' % (self.__class__.__name__, tuple(self)) - - def set_point_normal(self, point0, normal0, point1, normal1): - """Set the collision point and normal for both entities""" - self.info = ( - (self[0], point0, normal0), - (self[1], point1, normal1), - ) - - -class BroadSweepAndPrune(object): - """2D Broad-phase sweep and prune bounding box collision detector - - This algorithm is efficient for collision detection between many - moving bodies. It has linear algorithmic complexity and takes - advantage of temporal coherence between frames. It also does - not suffer from bad worst-case performance (like RDC can). - Unlike spacial hashing, it does not need to be optimized for - specific space and body sizes. - - Other algorithms may be more efficient for collision detection with - stationary bodies, bodies that are always evenly distributed, or ad-hoc - queries. - - :param collision_component: Name of the collision component used by this - system, defaults to 'collision'. This component supplies each - entities' aabb and collision masks. - :type collision_component: str - """ - world = None - """|World| object this system belongs to""" - - collision_component = None - """Name of world's collision component used by this system""" - - LEFT_ATTR = "left" - RIGHT_ATTR = "right" - TOP_ATTR = "top" - BOTTOM_ATTR = "bottom" - - def __init__(self, collision_component='collision'): - self.collision_component = collision_component - self._by_x = None - self._by_y = None - self._collision_pairs = None - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - def step(self, dt): - """Update the system for this time step, updates and sorts the - axis arrays. - """ - component = getattr(self.world.components, self.collision_component) - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - if self._by_x is None: - # Build axis lists from scratch - # Note we cache the box positions here - # so that we can perform hit tests efficiently - # it also isolates us from changes made to the - # box positions after we run - by_x = self._by_x = [] - append_x = by_x.append - by_y = self._by_y = [] - append_y = by_y.append - for data in component.itervalues(): - append_x([data.aabb.left, LEFT, data]) - append_x([data.aabb.right, RIGHT, data]) - append_y([data.aabb.bottom, BOTTOM, data]) - append_y([data.aabb.top, TOP, data]) - else: - by_x = self._by_x - by_y = self._by_y - removed = [] - for entry in by_x: - entry[0] = getattr(entry[2].aabb, entry[1]) - for entry in by_y: - entry[0] = getattr(entry[2].aabb, entry[1]) - # Removing entities is inefficient, but expected to be rare - if component.deleted_entities: - deleted_entities = component.deleted_entities - deleted_x = [] - deleted_y = [] - for i, (_, _, data) in enumerate(by_x): - if data.entity in deleted_entities: - deleted_x.append(i) - deleted_x.reverse() - for i in deleted_x: - del by_x[i] - for i, (_, _, data) in enumerate(by_y): - if data.entity in deleted_entities: - deleted_y.append(i) - deleted_y.reverse() - for i in deleted_y: - del by_y[i] - # Tack on new entities - for entity in component.new_entities: - data = component[entity] - by_x.append([data.aabb.left, LEFT, data]) - by_x.append([data.aabb.right, RIGHT, data]) - by_y.append([data.aabb.bottom, BOTTOM, data]) - by_y.append([data.aabb.top, TOP, data]) - - # Tim-sort is highly efficient with mostly sorted lists. - # Because positions tend to change little each frame - # we take advantage of this here. Obviously things are - # less efficient with very fast moving, or teleporting entities - by_x.sort() - by_y.sort() - self._collision_pairs = None - - @property - def collision_pairs(self): - """Set of candidate collision pairs for this timestep""" - if self._collision_pairs is None: - if self._by_x is None: - # Axis arrays not ready - return set() - - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - # Build candidates overlapping along the x-axis - component = getattr(self.world.components, self.collision_component) - xoverlaps = set() - add_xoverlap = xoverlaps.add - discard_xoverlap = xoverlaps.discard - open = {} - for _, side, data in self._by_x: - if side is LEFT: - for open_entity, (from_mask, into_mask) in open.iteritems(): - if data.from_mask & into_mask or from_mask & data.into_mask: - add_xoverlap(Pair(data.entity, open_entity)) - open[data.entity] = (data.from_mask, data.into_mask) - elif side is RIGHT: - del open[data.entity] - - if len(xoverlaps) <= 10 and len(xoverlaps)*4 < len(self._by_y): - # few candidates were found, so just scan the x overlap candidates - # along y. This requires an additional sort, but it should - # be cheaper than scanning everyone and its simpler - # than a separate brute-force check - entities = set([entity for entity, _ in xoverlaps] - + [entity for _, entity in xoverlaps]) - by_y = [] - for entity in entities: - data = component[entity] - # We can use tuples here, which are cheaper to create - by_y.append((data.aabb.bottom, BOTTOM, data)) - by_y.append((data.aabb.top, TOP, data)) - by_y.sort() - else: - by_y = self._by_y - - # Now check the candidates along the y-axis - open = set() - add_open = open.add - discard_open = open.discard - self._collision_pairs = set() - add_pair = self._collision_pairs.add - for _, side, data in by_y: - if side is BOTTOM: - for open_entity in open: - pair = Pair(data.entity, open_entity) - if pair in xoverlaps: - discard_xoverlap(pair) - add_pair(pair) - if not xoverlaps: - # No more candidates, bail - return self._collision_pairs - add_open(data.entity) - elif side is TOP: - discard_open(data.entity) - return self._collision_pairs - - def query_point(self, x_or_point, y=None, from_mask=0xffffffff): - """Hit test at the point specified. - - :param x_or_point: x coordinate (float) or sequence of (x, y) floats. - - :param y: y coordinate (float) if x is not a sequence - - :param from_mask: Bit mask used to filter query results. This value - is bit ANDed with candidate entities' ``collision.into_mask``. - If the result is non-zero, then it is considered a hit. By - default all entities colliding with the input point are - returned. - - :return: A set of entities where the point is inside their bounding - boxes as of the last time step. - """ - if self._by_x is None: - # Axis arrays not ready - return set() - if y is None: - x, y = x_or_point - else: - x = x_or_point - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - x_index = bisect_right(self._by_x, [x]) - x_hits = set() - add_x_hit = x_hits.add - discard_x_hit = x_hits.discard - if x_index <= len(self._by_x) // 2: - # closer to the left, scan from left to right - while (x == self._by_x[x_index][0] - and self._by_x[x_index][1] is LEFT - and x_index < len(self._by_x)): - # Ensure we hit on exact left edge matches - x_index += 1 - for _, side, data in self._by_x[:x_index]: - if side is LEFT and from_mask & data.into_mask: - add_x_hit(data.entity) - else: - discard_x_hit(data.entity) - else: - # closer to the right - for _, side, data in reversed(self._by_x[x_index:]): - if side is RIGHT and from_mask & data.into_mask: - add_x_hit(data.entity) - else: - discard_x_hit(data.entity) - if not x_hits: - return x_hits - - y_index = bisect_right(self._by_y, [y]) - y_hits = set() - add_y_hit = y_hits.add - discard_y_hit = y_hits.discard - if y_index <= len(self._by_y) // 2: - # closer to the bottom - while (y == self._by_y[y_index][0] - and self._by_y[y_index][1] is BOTTOM - and y_index < len(self._by_y)): - # Ensure we hit on exact bottom edge matches - y_index += 1 - for _, side, data in self._by_y[:y_index]: - if side is BOTTOM: - add_y_hit(data.entity) - else: - discard_y_hit(data.entity) - else: - # closer to the top - for _, side, data in reversed(self._by_y[y_index:]): - if side is TOP: - add_y_hit(data.entity) - else: - discard_y_hit(data.entity) - if y_hits: - return x_hits & y_hits - else: - return y_hits - - -class Circular(object): - """Basic narrow-phase collision detector which treats all entities as - circles with their radius defined in the collision component. - - :param handlers: A sequence of collision handler functions that are invoked - after collision detection. - :type handlers: sequence of functions - - :param collision_component: Name of collision component for this system, - defaults to 'collision'. This supplies each entity's collision - radius and masks. - :type collision_component: str - - :param position_component: Name of position component for this system, - defaults to 'position'. This supplies each entity's position. - :type position_component: str - - :param update_aabbs: If True (the default), then the entities' - `collision.aabb` fields will be updated using their position - and collision radius before invoking the broad phase system. - Set this False if another system updates the aabbs. - :type update_aabbs: bool - - :param broad_phase: A broad-phase collision system to use as a source - for collision pairs. If not specified, a :class:`BroadSweepAndPrune` - system will be created automatically. - """ - world = None - """|World| object this system belongs to""" - - position_component = None - """Name of world's position component used by this system""" - - collision_component = None - """Name of world's collision component used by this system""" - - update_aabbs = True - """Flag to indicate whether the system updates the entities' `collision.aabb` - field before invoking the broad phase collision system - """ - - handlers = None - """A sequence of collision handler functions invoke after collision - detection - """ - - broad_phase = None - """Broad phase collision system used as a source for collision pairs""" - - def __init__(self, handlers=(), position_component='position', - collision_component='collision', update_aabbs=True, broad_phase=None): - self.handlers = tuple(handlers) - if broad_phase is None: - broad_phase = BroadSweepAndPrune(collision_component) - self.collision_component = collision_component - self.position_component = position_component - self.update_aabbs = bool(update_aabbs) - self.broad_phase = broad_phase - self._collision_pairs = None - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - self.broad_phase.set_world(world) - for handler in self.handlers: - if hasattr(handler, 'set_world'): - handler.set_world(world) - - def step(self, dt): - """Update the collision system for this time step and invoke - the handlers - """ - if self.update_aabbs: - for position, collision in self.world.components.join( - self.position_component, self.collision_component): - aabb = collision.aabb - x, y = position.position - radius = collision.radius - aabb.left = x - radius - aabb.right = x + radius - aabb.bottom = y - radius - aabb.top = y + radius - self.broad_phase.step(dt) - self._collision_pairs = None - for handler in self.handlers: - handler(self) - - @property - def collision_pairs(self): - """The set of entity pairs in collision in this timestep""" - if self._collision_pairs is None: - position = getattr(self.world.components, self.position_component) - collision = getattr(self.world.components, self.collision_component) - pairs = self._collision_pairs = set() - for pair in self.broad_phase.collision_pairs: - entity1, entity2 = pair - position1 = position[entity1].position - position2 = position[entity2].position - radius1 = collision[entity1].radius - radius2 = collision[entity2].radius - separation = position2 - position1 - if separation.get_length_sqrd() <= (radius1 + radius2)**2: - normal = separation.normalized() - pair.set_point_normal( - normal * radius1 + position1, normal, - normal * -radius2 + position2, -normal) - pairs.add(pair) - return self._collision_pairs - - def query_point(self, x_or_point, y=None, from_mask=0xffffffff): - """Hit test at the point specified. - - :param x_or_point: x coordinate (float) or sequence of (x, y) floats. - - :param y: y coordinate (float) if x is not a sequence - - :param from_mask: Bit mask used to filter query results. This value - is bit ANDed with candidate entities' ``collision.into_mask``. - If the result is non-zero, then it is considered a hit. By - default all entities colliding with the input point are - returned. - - :return: A set of entities where the point is inside their collision - radii as of the last time step. - - """ - if y is None: - point = Vec2d(x_or_point) - else: - point = Vec2d(x_or_point, y) - hits = set() - position = getattr(self.world.components, self.position_component) - collision = getattr(self.world.components, self.collision_component) - for entity in self.broad_phase.query_point(x_or_point, y, from_mask): - separation = point - position[entity].position - if separation.get_length_sqrd() <= collision[entity].radius**2: - hits.add(entity) - return hits - - -def dispatch_events(collision_system): - """Collision handler that dispatches `on_collide()` events to entities - marked for collision by the specified collision system. The `on_collide()` - event handler methods are defined by the application on the desired entity - classes. These methods should have the following signature:: - - def on_collide(self, other_entity, collision_point, collision_normal): - '''Handle A collision between this entity and `other_entity` - - - other_entity (Entity): The other entity in collision with - `self` - - - collision_point (Vec2d): The point on this entity (`self`) - where the collision occurred. Note this may be `None` for - some collision systems that do not report it. - - - collision_normal (Vec2d): The normal vector at the point of - collision. As with `collision_point`, this may be None for - some collision systems. - ''' - - Note the arguments to `on_collide()` are always passed positionally, so you - can use different argument names than above if desired. - - If a pair of entities are in collision, then the event will be dispatched - to both objects in arbitrary order if all of their collision masks align. - """ - collision = getattr(collision_system.world.components, - collision_system.collision_component) - for pair in collision_system.collision_pairs: - entity1, entity2 = pair - if pair.info is not None: - args1, args2 = pair.info - else: - args1 = entity1, None, None - args2 = entity2, None, None - try: - on_collide = entity1.on_collide - masks_align = collision[entity2].from_mask & collision[entity1].into_mask - except (AttributeError, KeyError): - pass - else: - if masks_align: - on_collide(*args2) - try: - on_collide = entity2.on_collide - masks_align = collision[entity1].from_mask & collision[entity2].into_mask - except (AttributeError, KeyError): - pass - else: - if masks_align: - on_collide(*args1) diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/color.py --- a/bGrease/color.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ - -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 ab6a0fd1668a -r 2a12e2843984 bGrease/component/__init__.py --- a/bGrease/component/__init__.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -"""Components store all entity data in a given |World|. You can -think of components as tables with entities as their primary keys. Like -database tables, components are defined with a "schema" that specifies -the data fields. Each field in a component has a name and a type. - -Component objects themselves have a dict-like interface with entities -as keys and data records as values. An application will typically -interact with components via entity attributes, entity extents or -by joining them. For more information see: - -- :class:`~grease.entity.Entity` class. -- :class:`~grease.world.EntityExtent` class. -- :meth:`~grease.world.ComponentParts.join` method of ComponentParts. - -See also :ref:`defining custom components in the tutorial `. -""" - -__version__ = '$Id$' - -__all__ = ('Component', 'ComponentError', 'Position', 'Transform', 'Movement', - 'Shape', 'Renderable', 'Collision') - -from parpg.bGrease.component.general import Component -from parpg.bGrease.geometry import Vec2d, Vec2dArray, Rect -from parpg.bGrease import color - - -class ComponentError(Exception): - """General component error""" - - -class Position(Component): - """Predefined component that stores position and orientation info for - entities. - - Fields: - - - **position** (Vec2d) -- Position vector - - **angle** (float) -- Angle, in degrees - """ - - def __init__(self): - Component.__init__(self, position=Vec2d, angle=float) - - -class Transform(Component): - """Predefined component that stores offset, shear, - rotation and scale info for entity shapes. - - Fields: - - - **offset** (Vec2d) - - **shear** (Vec2d) - - **rotation** (float) - - **scale** (float, default 1.0) - """ - - def __init__(self): - Component.__init__(self, offset=Vec2d, shear=Vec2d, rotation=float, scale=float) - self.fields['scale'].default = lambda: 1.0 - - -class Movement(Component): - """Predefined component that stores velocity, - acceleration and rotation info for entities. - - Fields: - - - **velocity** (Vec2d) -- Rate of change of entity position - - **accel** (Vec2d) -- Rate of change of entity velocity - - **rotation** (Vec2d) -- Rate of change of entity angle, in degrees/time - """ - - def __init__(self): - Component.__init__(self, velocity=Vec2d, accel=Vec2d, rotation=float) - - -class Shape(Component): - """Predefined component that stores shape vertices for entities - - - **closed** (bool) -- If the shapes is closed implying an edge between - last and first vertices. - - **verts** (Vec2dArray) -- Array of vertex points - """ - - def __init__(self): - Component.__init__(self, closed=int, verts=Vec2dArray) - self.fields['closed'].default = lambda: 1 - - -class Renderable(Component): - """Predefined component that identifies entities to be - rendered and provides their depth and color. - - - **depth** (float) -- Drawing depth, can be used to determine z-order - while rendering. - - **color** (color.RGBA) -- Color used for entity. The effect of this - field depends on the renderer. - """ - - def __init__(self): - Component.__init__(self, depth=float, color=color.RGBA) - self.fields['color'].default = lambda: color.RGBA(1,1,1,1) - - -class Collision(Component): - """Predefined component that stores collision masks to determine - which entities can collide. - - Fields: - - - **aabb** (Rect) -- The axis-aligned bounding box for the entity. - This is used for broad-phase collision detection. - - - **radius** (float) -- The collision radius of the entity, used for narrow-phase - collision detection. The exact meaning of this value depends on the collision - system in use. - - - **from_mask** (int) -- A bitmask that determines what entities this object - can collide with. - - - **into_mask** (int) -- A bitmask that determines what entities can collide - with this object. - - When considering an entity A for collision with entity B, A's ``from_mask`` is - bit ANDed with B's ``into_mask``. If the result is nonzero (meaning 1 or more - bits is set the same for each) then the collision test is made. Otherwise, - the pair cannot collide. - - The default value for both of these masks is ``0xffffffff``, which means that - all entities will collide with each other by default. - """ - def __init__(self): - Component.__init__(self, aabb=Rect, radius=float, from_mask=int, into_mask=int) - self.fields['into_mask'].default = lambda: 0xffffffff - self.fields['from_mask'].default = lambda: 0xffffffff diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/component/base.py --- a/bGrease/component/base.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -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 ab6a0fd1668a -r 2a12e2843984 bGrease/component/field.py --- a/bGrease/component/field.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,303 +0,0 @@ -############################################################################# -# -# 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$' - -import operator -from parpg.bGrease.geometry import Vec2d, Vec2dArray, Rect -from parpg.bGrease import color - -# Allowed field types -> default values -types = {int:lambda: 0, - float:lambda: 0.0, - bool:lambda: False, - str:lambda:"", - object:lambda:None, - Vec2d:lambda: Vec2d(0,0), - Vec2dArray:lambda: Vec2dArray(), - color.RGBA: lambda: color.RGBA(0.0, 0.0, 0.0, 0.0), - Rect: lambda: Rect(0.0, 0.0, 0.0, 0.0), - list: lambda: list(), - dict: lambda: dict(), - } - -class Schema(dict): - """Field schema definition for custom components""" - - def __init__(self, **fields): - for ftype in fields.values(): - assert ftype in types, fname + " has an illegal field type" - self.update(fields) - - -class FieldAccessor(object): - """Facade for manipulating a field for a set of entities""" - - __field = None - __entities = None - __attrs = None - __getter = None - __parent_getters = () - - def __init__(self, field, entities, attrs=()): - self.__field = field - self.__entities = entities - field_getter = operator.attrgetter(field.name) - self.__attrs = attrs - if attrs: - getters = [field_getter] + [operator.attrgetter(attr) for attr in attrs] - def get(entity): - value = entity - for getter in getters: - value = getter(value) - return value - self.__getter = get - self.__parent_getters = getters[:-1] - else: - self.__getter = field_getter - - def __getattr__(self, name): - """Return a FieldAccessor for the child attribute""" - return self.__class__(self.__field, self.__entities, self.__attrs + (name,)) - - def __setattr__(self, name, value): - if value is self: - return # returned by mutators - if hasattr(self.__class__, name): - # Set local attr - self.__dict__[name] = value - elif not name.startswith('_'): - getattr(self, name).__set__(value) - else: - raise AttributeError("Cannot set field attribute: %s" % name) - - @property - def __setter(self): - """Return the proper setter function for setting the field value""" - if not self.__attrs: - return setattr - else: - parent_getters = self.__parent_getters - def setter(data, name, value): - for getter in parent_getters: - data = getter(data) - setattr(data, name, value) - self.__setter = setter - return setter - - def __set__(self, value): - """Set field values en masse""" - # Mass set field attr - setter = self.__setter - component = self.__field.component - if self.__attrs: - name = self.__attrs[-1] - else: - name = self.__field.name - if isinstance(value, FieldAccessor): - # Join set between two entity sets - if not self.__attrs: - cast = self.__field.cast - else: - cast = lambda x: x - for entity in self.__entities: - try: - setter(component[entity], name, cast(value[entity])) - except KeyError: - pass - else: - if not self.__attrs: - value = self.__field.cast(value) - for entity in self.__entities: - try: - setter(component[entity], name, value) - except KeyError: - pass - - def __getitem__(self, entity): - """Return the field value for a single entity (used for joins)""" - if entity in self.__entities: - return self.__getter(self.__field.component[entity]) - raise KeyError(entity) - - def __contains__(self, entity): - return entity in self.__entities - - def __repr__(self): - return '<%s %s @ %x>' % ( - self.__class__.__name__, - '.'.join((self.__field.name,) + self.__attrs), id(self)) - - def __nonzero__(self): - return bool(self.__entities) - - def __iter__(self): - """Return an iterator of all field values in the set""" - component = self.__field.component - getter = self.__getter - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - yield getter(data) - - ## batch comparison operators ## - - def __match(self, value, op): - component = self.__field.component - getter = self.__getter - matches = set() - add = matches.add - if isinstance(value, FieldAccessor): - # Join match between entity sets - for entity in self.__entities: - try: - data = component[entity] - other = value[entity] - except KeyError: - continue - if op(getter(data), other): - add(entity) - else: - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - if op(getter(data), value): - add(entity) - return matches - - def __eq__(self, value): - """Return an entity set of all entities with a matching field value""" - return self.__match(value, operator.eq) - - def __ne__(self, value): - """Return an entity set of all entities not matching field value""" - return self.__match(value, operator.ne) - - def __gt__(self, value): - """Return an entity set of all entities with a greater field value""" - return self.__match(value, operator.gt) - - def __ge__(self, value): - """Return an entity set of all entities with a greater or equal field value""" - return self.__match(value, operator.ge) - - def __lt__(self, value): - """Return an entity set of all entities with a lesser field value""" - return self.__match(value, operator.lt) - - def __le__(self, value): - """Return an entity set of all entities with a lesser or equal field value""" - return self.__match(value, operator.le) - - def _contains(self, values): - """Return an entity set of all entities with a field value contained in values""" - return self.__match(values, operator.contains) - - ## Batch in-place mutator methods - - def __mutate(self, value, op): - component = self.__field.component - if self.__attrs: - name = self.__attrs[-1] - else: - name = self.__field.name - getter = self.__getter - setter = self.__setter - if isinstance(value, FieldAccessor): - # Join between entity sets - for entity in self.__entities: - try: - data = component[entity] - other = value[entity] - except KeyError: - continue - setter(data, name, op(getter(data), other)) - else: - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - setter(data, name, op(getter(data), value)) - return self - - def __iadd__(self, value): - return self.__mutate(value, operator.iadd) - - def __isub__(self, value): - return self.__mutate(value, operator.isub) - - def __imul__(self, value): - return self.__mutate(value, operator.imul) - - def __idiv__(self, value): - return self.__mutate(value, operator.idiv) - - def __itruediv__(self, value): - return self.__mutate(value, operator.itruediv) - - def __ifloordiv__(self, value): - return self.__mutate(value, operator.ifloordiv) - - def __imod__(self, value): - return self.__mutate(value, operator.imod) - - def __ipow__(self, value): - return self.__mutate(value, operator.ipow) - - def __ilshift__(self, value): - return self.__mutate(value, operator.ilshift) - - def __irshift__(self, value): - return self.__mutate(value, operator.irshift) - - def __iand__(self, value): - return self.__mutate(value, operator.iand) - - def __ior__(self, value): - return self.__mutate(value, operator.ior) - - def __ixor__(self, value): - return self.__mutate(value, operator.ixor) - - -class Field(object): - """Component field metadata and accessor interface""" - - def __init__(self, component, name, type, accessor_factory=FieldAccessor): - self.component = component - self.name = name - self.type = type - self.default = types.get(type) - self.accessor_factory = accessor_factory - - def cast(self, value): - """Cast value to the appropriate type for thi field""" - if self.type is not object: - return self.type(value) - else: - return value - - def accessor(self, entities=None): - """Return the field accessor for the entities in the component, - or all entities in the set specified that are also in the component - """ - if entities is None or entities is self.component.entities: - entities = self.component.entities - else: - entities = entities & self.component.entities - return self.accessor_factory(self, entities) diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/component/general.py --- a/bGrease/component/general.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -############################################################################# -# -# 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$' - -from parpg.bGrease.component import base -from parpg.bGrease.component import field -from parpg.bGrease.entity import ComponentEntitySet - - -class Component(dict): - """General component with a configurable schema - - The field schema is defined via keyword args where the - arg name is the field name and the value is the type object. - - The following types are supported for fields: - - - :class:`int` - - :class:`float` - - :class:`bool` - - :class:`str` - - :class:`object` - - |Vec2d| - - |Vec2dArray| - - |RGBA| - - |Rect| - """ - - deleted_entities = () - """List of entities deleted from the component since the last time step""" - - new_entities = () - """List of entities added to the component since the last time step""" - - def __init__(self, **fields): - self.fields = {} - for fname, ftype in fields.items(): - assert ftype in field.types, fname + " has an illegal field type" - self.fields[fname] = field.Field(self, fname, ftype) - self.entities = ComponentEntitySet(self) - self._added = [] - self._deleted = [] - - def set_world(self, world): - self.world = world - - def step(self, dt): - """Update the component for the next timestep""" - delitem = super(Component, self).__delitem__ - for entity in self._deleted: - delitem(entity) - self.new_entities = self._added - self.deleted_entities = self._deleted - self._added = [] - self._deleted = [] - - def set(self, entity, data=None, **data_kw): - """Set the component data for an entity, adding it to the - component if it is not already a member. - - If data is specified, its data for the new entity's fields are - copied from its attributes, making it easy to copy another - entity's data. Keyword arguments are also matched to fields. - If both a data attribute and keyword argument are supplied for - a single field, the keyword arg is used. - """ - if data is not None: - for fname, field in self.fields.items(): - if fname not in data_kw and hasattr(data, fname): - data_kw[fname] = getattr(data, fname) - data = self[entity] = Data(self.fields, entity, **data_kw) - return data - - def __setitem__(self, entity, data): - assert entity.world is self.world, "Entity not in component's world" - if entity not in self.entities: - self._added.append(entity) - self.entities.add(entity) - super(Component, self).__setitem__(entity, data) - - def remove(self, entity): - if entity in self.entities: - self._deleted.append(entity) - self.entities.remove(entity) - return True - return False - - __delitem__ = remove - - def __repr__(self): - return '<%s %x of %r>' % ( - self.__class__.__name__, id(self), getattr(self, 'world', None)) - - -class Singleton(Component): - """Component that may contain only a single entity""" - - def add(self, entity_id, data=None, **data_kw): - if entity_id not in self._data: - self.entity_id_set.clear() - self._data.clear() - Component.add(self, entity_id, data, **data_kw) - - @property - def entity(self): - """Return the entity in the component, or None if empty""" - if self._data: - return self.manager[self._data.keys()[0]] - - -class Data(object): - - def __init__(self, fields, entity, **data): - self.__dict__['_Data__fields'] = fields - self.__dict__['entity'] = entity - for field in fields.values(): - if field.name in data: - setattr(self, field.name, data[field.name]) - else: - setattr(self, field.name, field.default()) - - def __setattr__(self, name, value): - if name in self.__fields: - self.__dict__[name] = self.__fields[name].cast(value) - else: - raise AttributeError("Invalid data field: " + name) - - def __repr__(self): - return '<%s(%r)>' % (self.__class__.__name__, self.__dict__) - - diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/component/schema.py --- a/bGrease/component/schema.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ - diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/controller/__init__.py --- a/bGrease/controller/__init__.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ - -__all__ = ('EulerMovement',) - -from parpg.bGrease.controller.integrator import EulerMovement diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/controller/integrator.py --- a/bGrease/controller/integrator.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -############################################################################# -# -# 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 ab6a0fd1668a -r 2a12e2843984 bGrease/entity.py --- a/bGrease/entity.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,212 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -"""Grease entities are useful as actionable, interactive -game elements that are often visible to the player. - -You might use entities to represent: - -- Characters -- Bullets -- Particles -- Pick-ups -- Space Ships -- Weapons -- Trees -- Planets -- Explosions - -See :ref:`an example entity class in the tutorial `. -""" - -__version__ = '$Id$' - -__all__ = ('Entity', 'EntityComponentAccessor', 'ComponentEntitySet') - - -class EntityMeta(type): - """The entity metaclass enforces fixed slots of `entity_id` and `world` - for all subclasses. This prevents accidental use of other entity instance - attributes, which may not be saved. - - Class attributes are not affected by this restriction, but subclasses - should be careful not to cause name collisions with world components, - which are exposed as entity attributes. Using a naming convention for - class attributes, such as UPPER_CASE_WITH_UNDERSCORES is recommended to - avoid name clashes. - - Note as a result of this, entity subclasses are not allowed to define - `__slots__`, and doing so will cause a `TypeError` to be raised. - """ - - def __new__(cls, name, bases, clsdict): - if '__slots__' in clsdict: - raise TypeError('__slots__ may not be defined in Entity subclasses') - clsdict['__slots__'] = ('world', 'entity_id') - return type.__new__(cls, name, bases, clsdict) - - -class Entity(object): - """Base class for grease entities. - - Entity objects themselves are merely identifiers within a :class:`grease.world.World`. - They also provide a facade for convenient entity-wise access of component - data. However, they do not contain any data themselves other than an - entity id. - - Entities must be instantiated in the context of a world. To instantiate an - entity, you must pass the world as the first argument to the constructor. - Subclasses that implement the :meth:`__init__()` method, must accept the world - as their first argument (after ``self``). Other constructor arguments can be - specified arbitarily by the subclass. - """ - __metaclass__ = EntityMeta - - def __new__(cls, world, *args, **kw): - """Create a new entity and add it to the world""" - entity = object.__new__(cls) - entity.world = world - entity.entity_id = world.new_entity_id() - world.entities.add(entity) - return entity - - def __getattr__(self, name): - """Return an :class:`EntityComponentAccessor` for this entity - for the component named. - - Example:: - - my_entity.movement - """ - component = getattr(self.world.components, name) - return EntityComponentAccessor(component, self) - - def __setattr__(self, name, value): - """Set the entity data in the named component for this entity. - This sets the values of the component fields to the values of - the matching attributes of the value provided. This value must - have attributes for each of the component fields. - - This allows you to easily copy component data from one entity - to another. - - Example:: - - my_entity.position = other_entity.position - """ - if name in self.__class__.__slots__: - super(Entity, self).__setattr__(name, value) - else: - component = getattr(self.world.components, name) - component.set(self, value) - - def __delattr__(self, name): - """Remove this entity and its data from the component. - - Example:: - - del my_entity.renderable - """ - component = getattr(self.world.components, name) - del component[self] - - def __hash__(self): - return self.entity_id - - def __eq__(self, other): - return self.world is other.world and self.entity_id == other.entity_id - - def __repr__(self): - return "<%s id: %s of %s %x>" % ( - self.__class__.__name__, self.entity_id, - self.world.__class__.__name__, id(self.world)) - - def delete(self): - """Delete the entity from its world. This removes all of its - component data. If then entity has already been deleted, - this call does nothing. - """ - self.world.entities.discard(self) - - @property - def exists(self): - """True if the entity still exists in the world""" - return self in self.world.entities - - -class EntityComponentAccessor(object): - """A facade for accessing specific component data for a single entity. - The implementation is lazy and does not actually access the component - data until needed. If an attribute is set for a component that the - entity is not yet a member of, it is automatically added to the - component first. - - :param component: The :class:`grease.Component` being accessed - :param entity: The :class:`Entity` being accessed - """ - - # beware, name mangling ahead. We want to avoid clashing with any - # user-configured component field names - __data = None - - def __init__(self, component, entity): - clsname = self.__class__.__name__ - self.__dict__['_%s__component' % clsname] = component - self.__dict__['_%s__entity' % clsname] = entity - - def __nonzero__(self): - """The accessor is True if the entity is in the component, - False if not, for convenient membership tests - """ - return self.__entity in self.__component - - def __getattr__(self, name): - """Return the data for the specified field of the entity's component""" - if self.__data is None: - try: - data = self.__component[self.__entity] - except KeyError: - raise AttributeError(name) - clsname = self.__class__.__name__ - self.__dict__['_%s__data' % clsname] = data - return getattr(self.__data, name) - - def __setattr__(self, name, value): - """Set the data for the specified field of the entity's component""" - if self.__data is None: - clsname = self.__class__.__name__ - if self.__entity in self.__component: - self.__dict__['_%s__data' % clsname] = self.__component[self.__entity] - else: - self.__dict__['_%s__data' % clsname] = self.__component.set(self.__entity) - setattr(self.__data, name, value) - - -class ComponentEntitySet(set): - """Set of entities in a component, can be queried by component fields""" - - _component = None - - def __init__(self, component, entities=()): - self.__dict__['_component'] = component - super(ComponentEntitySet, self).__init__(entities) - - def __getattr__(self, name): - if self._component is not None and name in self._component.fields: - return self._component.fields[name].accessor(self) - raise AttributeError(name) - - def __setattr__(self, name, value): - if self._component is not None and name in self._component.fields: - self._component.fields[name].accessor(self).__set__(value) - raise AttributeError(name) - diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/geometry.py --- a/bGrease/geometry.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,600 +0,0 @@ -__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 ab6a0fd1668a -r 2a12e2843984 bGrease/impl/__init__.py --- a/bGrease/impl/__init__.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -############################################################################# -# -# 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 ab6a0fd1668a -r 2a12e2843984 bGrease/impl/controls.py --- a/bGrease/impl/controls.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,216 +0,0 @@ -############################################################################# -# -# 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 ab6a0fd1668a -r 2a12e2843984 bGrease/impl/mode.py --- a/bGrease/impl/mode.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -############################################################################# -# -# 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 -import pyglet -from parpg.bGrease.mode import * - -class PygletManager(BaseManager): - """Mode manager abstract base class using pyglet. - - 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. - """ - - event_dispatcher = None - """:class:`pyglet.event.EventDispatcher` object that the - active mode receive events from. - """ - - def activate_mode(self, mode): - """Perform actions to activate a node - - :param mode: The :class: 'Mode' object to activate - """ - BaseManager.activate_mode(self, mode) - self.event_dispatcher.push_handlers(mode) - - def deactivate_mode(self, mode): - """Perform actions to deactivate a node - - :param mode: The :class: 'Mode' object to deactivate - """ - BaseManager.deactivate_mode(self, mode) - self.event_dispatcher.remove_handlers(mode) - -class Manager(PygletManager): - """A basic mode manager that wraps a single - :class:`pyglet.event.EventDispatcher` object for use by its modes. - """ - - def __init__(self, event_dispatcher): - self.modes = [] - self.event_dispatcher = event_dispatcher - - -class ManagerWindow(PygletManager, pyglet.window.Window): - """An integrated mode manager and pyglet window for convenience. - The window is the event dispatcher used by modes pushed to - this manager. - - Constructor arguments are identical to :class:`pyglet.window.Window` - """ - - def __init__(self, *args, **kw): - super(ManagerWindow, self).__init__(*args, **kw) - self.modes = [] - self.event_dispatcher = self - - def on_key_press(self, symbol, modifiers): - """Default :meth:`on_key_press handler`, pops the current mode on ``ESC``""" - if symbol == pyglet.window.key.ESCAPE: - self.pop_mode() - - def on_last_mode_pop(self, mode): - """Hook executed when the last mode is popped from the manager. - When the last mode is popped from a window, an :meth:`on_close` event - is dispatched. - - :param mode: The :class:`Mode` object just popped from the manager - """ - self.dispatch_event('on_close') - - -class Mode(BaseMode): - """Application mode abstract base class using pyglet - - Subclasses must implement the :meth:`step` method - - :param step_rate: The rate of :meth:`step()` calls per second. - - :param master_clock: The :class:`pyglet.clock.Clock` interface used - as the master clock that ticks the world's clock. This - defaults to the main pyglet clock. - """ - clock = None - """The :class:`pyglet.clock.Clock` instance used as this mode's clock. - You should use this clock to schedule tasks for this mode, so they - properly respect when the mode is active or inactive - - Example:: - - my_mode.clock.schedule_once(my_cool_function, 4) - """ - - def __init__(self, step_rate=60, master_clock=pyglet.clock, - clock_factory=pyglet.clock.Clock): - BaseMode.__init__(self) - self.step_rate = step_rate - self.time = 0.0 - self.master_clock = master_clock - self.clock = clock_factory(time_function=lambda: self.time) - self.clock.schedule_interval(self.step, 1.0 / step_rate) - - def on_activate(self): - """Being called when the Mode is activated""" - self.master_clock.schedule(self.tick) - - def on_deactivate(self): - """Being called when the Mode is deactivated""" - self.master_clock.unschedule(self.tick) - - def tick(self, dt): - """Tick the mode's clock. - - :param dt: The time delta since the last tick - :type dt: float - """ - self.time += dt - self.clock.tick(poll=False) - - @abc.abstractmethod - def step(self, dt): - """Execute a timestep for this mode. Must be defined by subclasses. - - :param dt: The time delta since the last time step - :type dt: float - """ - -class Multi(BaseMulti, Mode): - """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. - """ - - def __init__(self, submodes): - BaseMulti.__init__(self, submodes) - self.time = 0.0 - - - def _set_active_submode(self, submode): - BaseMulti._set_active_submode(self, submode) - self.master_clock = submode.master_clock - self.clock = submode.clock - - def clear_subnode(self): - """Clear any subnmode data""" - BaseMulti.clear_subnode(self) - self.master_clock = None - self.clock = None - - def tick(self, dt): - """Tick the active submode's clock. - - :param dt: The time delta since the last tick - :type dt: float - """ - self.time += dt - if self.active_submode is not None: - self.active_submode.clock.tick(poll=False) diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/impl/world.py --- a/bGrease/impl/world.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -"""Worlds are environments described by a configuration of components, systems and -renderers. These parts describe the data, behavioral and presentation aspects -of the world respectively. - -The world environment is the context within which entities exist. A typical -application consists of one or more worlds containing entities that evolve -over time and react to internal and external interaction. - -See :ref:`an example of world configuration in the tutorial `. -""" - -__version__ = '$Id$' - -import itertools -import pyglet -from pyglet import gl -from parpg.bGrease.world import * -from parpg.bGrease.impl import Mode - -class World(Mode, BaseWorld): - """A coordinated collection of components, systems and entities - - A world is also a mode that may be pushed onto a - :class:`grease.mode.Manager` - - :param step_rate: The rate of :meth:`step()` calls per second. - - :param master_clock: The :class:`pyglet.clock.Clock` interface used - as the master clock that ticks the world's clock. This - defaults to the main pyglet clock. - """ - - clock = None - """:class:`pyglet.clock` interface for use by constituents - of the world for scheduling - """ - - time = None - """Current clock time of the world, starts at 0 when the world - is instantiated - """ - - running = True - """Flag to indicate that the world clock is running, advancing time - and stepping the world. Set running to False to pause the world. - """ - - def __init__(self, step_rate=60, master_clock=pyglet.clock, - clock_factory=pyglet.clock.Clock): - Mode.__init__(self, step_rate, master_clock, clock_factory) - BaseWorld.__init__(self) - - def activate(self, manager): - """Activate the world/mode for the given manager, if the world is already active, - do nothing. This method is typically not used directly, it is called - automatically by the mode manager when the world becomes active. - - The systems of the world are pushed onto `manager.event_dispatcher` - so they can receive system events. - - :param manager: :class:`mode.BaseManager` instance - """ - if not self.active: - for system in self.systems: - manager.event_dispatcher.push_handlers(system) - super(World, self).activate(manager) - - def deactivate(self, manager): - """Deactivate the world/mode, if the world is not active, do nothing. - This method is typically not used directly, it is called - automatically by the mode manager when the world becomes active. - - Removes the system handlers from the `manager.event_dispatcher` - - :param manager: :class:`mode.BaseManager` instance - """ - for system in self.systems: - manager.event_dispatcher.remove_handlers(system) - super(World, self).deactivate(manager) - - def tick(self, dt): - """Tick the mode's clock, but only if the world is currently running - - :param dt: The time delta since the last tick - :type dt: float - """ - if self.running: - super(World, self).tick(dt) - - def step(self, dt): - """Execute a time step for the world. Updates the world `time` - and invokes the world's systems. - - Note that the specified time delta will be pinned to 10x the - configured step rate. For example if the step rate is 60, - then dt will be pinned at a maximum of 0.1666. This avoids - pathological behavior when the time between steps goes - much longer than expected. - - :param dt: The time delta since the last time step - :type dt: float - """ - dt = min(dt, 10.0 / self.step_rate) - for component in self.components: - if hasattr(component, "step"): - component.step(dt) - for system in self.systems: - if hasattr(system, "step"): - system.step(dt) - - def on_draw(self, gl=pyglet.gl): - """Clear the current OpenGL context, reset the model/view matrix and - invoke the `draw()` methods of the renderers in order - """ - gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) - gl.glLoadIdentity() - BaseWorld.draw_renderers(self) \ No newline at end of file diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/mode.py --- a/bGrease/mode.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,391 +0,0 @@ -############################################################################# -# -# 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 ab6a0fd1668a -r 2a12e2843984 bGrease/renderer/__init__.py --- a/bGrease/renderer/__init__.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -"""Renderers define world presentation. This module contains the -built-in renderer classes. - -See also: - -- :class:`~grease.Renderer` abstract base class. -- :ref:`Example renderer class in the tutorial ` -""" - -__all__ = ('Vector', 'Camera') - -from parpg.bGrease.renderer.vector import Vector -from parpg.bGrease.renderer.camera import Camera diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/renderer/camera.py --- a/bGrease/renderer/camera.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,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) - diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/renderer/vector.py --- a/bGrease/renderer/vector.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -############################################################################# -# -# 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$' - -__all__ = ('Vector',) - -from parpg.bGrease.geometry import Vec2d -import ctypes -from math import sin, cos, radians -import pyglet - - -class Vector(object): - """Renders shapes in a classic vector graphics style - - :param scale: Scaling factor applied to shape vertices when rendered. - - :param line_width: The line width provided to ``glLineWidth`` before rendering. - If not specified or None, ``glLineWidth`` is not called, and the line - width used is determined by the OpenGL state at the time of rendering. - - :param anti_alias: If ``True``, OpenGL blending and line smoothing is enabled. - This allows for fractional line widths as well. If ``False``, the blending - and line smoothing modes are unchanged. - - :param corner_fill: If true (the default), the shape corners will be filled - with round points when the ``line_width`` exceeds 2.0. This improves - the visual quality of the rendering at larger line widths at some - cost to performance. Has no effect if ``line_width`` is not specified. - - :param position_component: Name of :class:`grease.component.Position` - component to use. Shapes rendered are offset by the entity positions. - - :param renderable_component: Name of :class:`grease.component.Renderable` - component to use. This component specifies the entities to be - rendered and their base color. - - :param shape_component: Name of :class:`grease.component.Shape` - component to use. Source of the shape vertices for each entity. - - The entities rendered are taken from the intersection of he position, - renderable and shape components each time :meth:`draw` is called. - """ - - CORNER_FILL_SCALE = 0.6 - CORNER_FILL_THRESHOLD = 2.0 - - def __init__(self, scale=1.0, line_width=None, anti_alias=True, corner_fill=True, - position_component='position', - renderable_component='renderable', - shape_component='shape'): - self.scale = float(scale) - self.corner_fill = corner_fill - self.line_width = line_width - self.anti_alias = anti_alias - self._max_line_width = None - self.position_component = position_component - self.renderable_component = renderable_component - self.shape_component = shape_component - - def set_world(self, world): - self.world = world - - def _generate_verts(self): - """Generate vertex and index arrays for rendering""" - vert_count = sum(len(shape.verts) + 1 - for shape, ignored, ignored in self.world.components.join( - self.shape_component, self.position_component, self.renderable_component)) - v_array = (CVertColor * vert_count)() - if vert_count > 65536: - i_array = (ctypes.c_uint * 2 * vert_count)() - i_size = pyglet.gl.GL_UNSIGNED_INT - else: - i_array = (ctypes.c_ushort * (2 * vert_count))() - i_size = pyglet.gl.GL_UNSIGNED_SHORT - v_index = 0 - i_index = 0 - scale = self.scale - rot_vec = Vec2d(0, 0) - for shape, position, renderable in self.world.components.join( - self.shape_component, self.position_component, self.renderable_component): - shape_start = v_index - angle = radians(-position.angle) - rot_vec.x = cos(angle) - rot_vec.y = sin(angle) - r = int(renderable.color.r * 255) - g = int(renderable.color.g * 255) - b = int(renderable.color.b * 255) - a = int(renderable.color.a * 255) - for vert in shape.verts: - vert = vert.cpvrotate(rot_vec) * scale + position.position - v_array[v_index].vert.x = vert.x - v_array[v_index].vert.y = vert.y - v_array[v_index].color.r = r - v_array[v_index].color.g = g - v_array[v_index].color.b = b - v_array[v_index].color.a = a - if v_index > shape_start: - i_array[i_index] = v_index - 1 - i_index += 1 - i_array[i_index] = v_index - i_index += 1 - v_index += 1 - if shape.closed and v_index - shape_start > 2: - i_array[i_index] = v_index - 1 - i_index += 1 - i_array[i_index] = shape_start - i_index += 1 - return v_array, i_size, i_array, i_index - - def draw(self, gl=pyglet.gl): - vertices, index_size, indices, index_count = self._generate_verts() - if index_count: - if self.anti_alias: - gl.glEnable(gl.GL_LINE_SMOOTH) - gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glPushClientAttrib(gl.GL_CLIENT_VERTEX_ARRAY_BIT) - gl.glEnableClientState(gl.GL_VERTEX_ARRAY) - gl.glEnableClientState(gl.GL_COLOR_ARRAY) - gl.glVertexPointer( - 2, gl.GL_FLOAT, ctypes.sizeof(CVertColor), ctypes.pointer(vertices)) - gl.glColorPointer( - 4, gl.GL_UNSIGNED_BYTE, ctypes.sizeof(CVertColor), - ctypes.pointer(vertices[0].color)) - if self.line_width is not None: - gl.glLineWidth(self.line_width) - if self._max_line_width is None: - range_out = (ctypes.c_float * 2)() - gl.glGetFloatv(gl.GL_ALIASED_LINE_WIDTH_RANGE, range_out) - self._max_line_width = float(range_out[1]) * self.CORNER_FILL_SCALE - if self.corner_fill and self.line_width > self.CORNER_FILL_THRESHOLD: - gl.glEnable(gl.GL_POINT_SMOOTH) - gl.glPointSize( - min(self.line_width * self.CORNER_FILL_SCALE, self._max_line_width)) - gl.glDrawArrays(gl.GL_POINTS, 0, index_count) - gl.glDrawElements(gl.GL_LINES, index_count, index_size, ctypes.pointer(indices)) - gl.glPopClientAttrib() - - -class CVert(ctypes.Structure): - _fields_ = [("x", ctypes.c_float), ("y", ctypes.c_float)] - -class CColor(ctypes.Structure): - _fields_ = [ - ("r", ctypes.c_ubyte), - ("g", ctypes.c_ubyte), - ("b", ctypes.c_ubyte), - ("a", ctypes.c_ubyte), - ] - -class CVertColor(ctypes.Structure): - _fields_ = [("vert", CVert), ("color", CColor)] diff -r ab6a0fd1668a -r 2a12e2843984 bGrease/world.py --- a/bGrease/world.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,301 +0,0 @@ -############################################################################# -# -# 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. -# -############################################################################# -"""Worlds are environments described by a configuration of components, systems and -renderers. These parts describe the data, behavioral and presentation aspects -of the world respectively. - -The world environment is the context within which entities exist. A typical -application consists of one or more worlds containing entities that evolve -over time and react to internal and external interaction. - -See :ref:`an example of world configuration in the tutorial `. -""" - -__version__ = '$Id$' - -import itertools -from parpg.bGrease import mode -from parpg.bGrease.component import ComponentError -from parpg.bGrease.entity import Entity, ComponentEntitySet - - -class BaseWorld(object): - """A coordinated collection of components, systems and entities - - A world is also a mode that may be pushed onto a - :class:`grease.mode.Manager` - """ - - components = None - """:class:`ComponentParts` object containing all world components. - :class:`grease.component.Component` objects define and contain all entity data - """ - - systems = None - """:class:`Parts` object containing all world systems. - :class:`grease.System` objects define world and entity behavior - """ - - renderers = None - """:class:`Parts` object containing all world renderers. - :class:`grease.Renderer` objects define world presentation - """ - - entities = None - """Set of all entities that exist in the world""" - - def __init__(self): - self.components = ComponentParts(self) - self.systems = Parts(self) - self.renderers = Parts(self) - self.new_entity_id = itertools.count().next - self.new_entity_id() # skip id 0 - self.entities = WorldEntitySet(self) - self._full_extent = EntityExtent(self, self.entities) - self._extents = {} - self.configure() - - def configure(self): - """Hook to configure the world after construction. This method - is called immediately after the world is initialized. Override - in a subclass to configure the world's components, systems, - and renderers. - - The default implementation does nothing. - """ - - def __getitem__(self, entity_class): - """Return an :class:`EntityExtent` for the given entity class. This extent - can be used to access the set of entities of that class in the world - or to query these entities via their components. - - Examples:: - - world[MyEntity] - world[...] - - :param entity_class: The entity class for the extent. - - May also be a tuple of entity classes, in which case - the extent returned contains union of all entities of the classes - in the world. - - May also be the special value ellipsis (``...``), which - returns an extent containing all entities in the world. This allows - you to conveniently query all entities using ``world[...]``. - """ - if isinstance(entity_class, tuple): - entities = set() - for cls in entity_class: - if cls in self._extents: - entities |= self._extents[cls].entities - return EntityExtent(self, entities) - elif entity_class is Ellipsis: - return self._full_extent - try: - return self._extents[entity_class] - except KeyError: - extent = self._extents[entity_class] = EntityExtent(self, set()) - return extent - - def draw_renderers(self): - """Draw all renderers""" - for renderer in self.renderers: - renderer.draw() - -class WorldEntitySet(set): - """Entity set for a :class:`World`""" - - def __init__(self, world): - self.world = world - - def add(self, entity): - """Add the entity to the set and all necessary class sets - Return the unique entity id for the entity, creating one - as needed. - """ - super(WorldEntitySet, self).add(entity) - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.add(entity) - - def remove(self, entity): - """Remove the entity from the set and, world components, - and all necessary class sets - """ - super(WorldEntitySet, self).remove(entity) - for component in self.world.components: - try: - del component[entity] - except KeyError: - pass - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.discard(entity) - - def discard(self, entity): - """Remove the entity from the set if it exists, if not, - do nothing - """ - try: - self.remove(entity) - except KeyError: - pass - - -class EntityExtent(object): - """Encapsulates a set of entities queriable by component. Extents - are accessed by using an entity class as a key on the :class:`World`:: - - extent = world[MyEntity] - """ - - entities = None - """The full set of entities in the extent""" - - def __init__(self, world, entities): - self.__world = world - self.entities = entities - - def __getattr__(self, name): - """Return a queriable :class:`ComponentEntitySet` for the named component - - Example:: - - world[MyEntity].movement.velocity > (0, 0) - - Returns a set of entities where the value of the :attr:`velocity` field - of the :attr:`movement` component is greater than ``(0, 0)``. - """ - component = getattr(self.__world.components, name) - return ComponentEntitySet(component, self.entities & component.entities) - - -class Parts(object): - """Maps world parts to attributes. The parts are kept in the - order they are set. Parts may also be inserted out of order. - - Used for: - - - :attr:`World.systems` - - :attr:`World.renderers` - """ - - _world = None - _parts = None - _reserved_names = ('entities', 'entity_id', 'world') - - def __init__(self, world): - self._world = world - self._parts = [] - - def _validate_name(self, name): - if (name in self._reserved_names or name.startswith('_') - or hasattr(self.__class__, name)): - raise ComponentError('illegal part name: %s' % name) - return name - - def __setattr__(self, name, part): - if not hasattr(self.__class__, name): - self._validate_name(name) - if not hasattr(self, name): - self._parts.append(part) - else: - old_part = getattr(self, name) - self._parts[self._parts.index(old_part)] = part - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - elif name.startswith("_"): - super(Parts, self).__setattr__(name, part) - else: - raise AttributeError("%s attribute is read only" % name) - - def __delattr__(self, name): - self._validate_name(name) - part = getattr(self, name) - self._parts.remove(part) - super(Parts, self).__delattr__(name) - - def insert(self, name, part, before=None, index=None): - """Add a part with a particular name at a particular index. - If a part by that name already exists, it is replaced. - - :arg name: The name of the part. - :type name: str - - :arg part: The component, system, or renderer part to insert - - :arg before: A part object or name. If specified, the part is - inserted before the specified part in order. - - :arg index: If specified, the part is inserted in the position - specified. You cannot specify both before and index. - :type index: int - """ - assert before is not None or index is not None, ( - "Must specify a value for 'before' or 'index'") - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments when inserting") - self._validate_name(name) - if before is not None: - if isinstance(before, str): - before = getattr(self, before) - index = self._parts.index(before) - if hasattr(self, name): - old_part = getattr(self, name) - self._parts.remove(old_part) - self._parts.insert(index, part) - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - - def __iter__(self): - """Iterate the parts in order""" - return iter(tuple(self._parts)) - - def __len__(self): - return len(self._parts) - - -class ComponentParts(Parts): - """Maps world components to attributes. The components are kept in the - order they are set. Components may also be inserted out of order. - - Used for: :attr:`World.components` - """ - - def join(self, *component_names): - """Join and iterate entity data from multiple components together. - - For each entity in all of the components named, yield a tuple containing - the entity data from each component specified. - - This is useful in systems that pull data from multiple components. - - Typical Usage:: - - for position, movement in world.components.join("position", "movement"): - # Do something with each entity's position and movement data - """ - if component_names: - components = [getattr(self, self._validate_name(name)) - for name in component_names] - if len(components) > 1: - entities = components[0].entities & components[1].entities - for comp in components[2:]: - entities &= comp.entities - else: - entities = components[0].entities - for entity in entities: - yield tuple(comp[entity] for comp in components) diff -r ab6a0fd1668a -r 2a12e2843984 charactercreationcontroller.py --- a/charactercreationcontroller.py Sat Mar 24 09:59:46 2012 +0100 +++ b/charactercreationcontroller.py Tue Mar 27 13:41:20 2012 +0200 @@ -19,7 +19,7 @@ from controllerbase import ControllerBase from gamescenecontroller import GameSceneController from gamesceneview import GameSceneView -from parpg.world import World +from parpg.world import PARPGWorld from parpg.entities import General from parpg.components import character_statistics @@ -50,7 +50,7 @@ else: return 10 -class CharacterCreationController(ControllerBase, World): +class CharacterCreationController(ControllerBase, PARPGWorld): """Controller defining the behaviour of the character creation screen.""" #TODO: Change to actual values @@ -77,7 +77,7 @@ @type application: L{fife.extensions.basicapplication.ApplicationBase}""" ControllerBase.__init__(self, engine, view, model, application) - World.__init__(self) + PARPGWorld.__init__(self) self.settings = self.model.settings self.view.start_new_game_callback = self.startNewGame self.view.cancel_new_game_callback = self.cancelNewGame diff -r ab6a0fd1668a -r 2a12e2843984 components/base.py --- a/components/base.py Sat Mar 24 09:59:46 2012 +0100 +++ b/components/base.py Tue Mar 27 13:41:20 2012 +0200 @@ -11,7 +11,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from parpg.bGrease.component import Component +from bGrease.component import Component class Base(Component): """Base component for PARPG.""" diff -r ab6a0fd1668a -r 2a12e2843984 components/change_map.py --- a/components/change_map.py Sat Mar 24 09:59:46 2012 +0100 +++ b/components/change_map.py Tue Mar 27 13:41:20 2012 +0200 @@ -12,7 +12,7 @@ # along with this program. If not, see . from base import Base -from parpg.bGrease.geometry import Vec2d +from bGrease.geometry import Vec2d class ChangeMap(Base): """Component that allows an entity to be contained by Container entity.""" diff -r ab6a0fd1668a -r 2a12e2843984 controllerbase.py --- a/controllerbase.py Sat Mar 24 09:59:46 2012 +0100 +++ b/controllerbase.py Tue Mar 27 13:41:20 2012 +0200 @@ -17,9 +17,9 @@ from parpg.common.listeners.key_listener import KeyListener from parpg.common.listeners.mouse_listener import MouseListener from parpg.common.listeners.command_listener import CommandListener -from parpg.mode import FifeMode +from bGrease.grease_fife.mode import Mode -class ControllerBase(FifeMode, KeyListener, MouseListener, CommandListener): +class ControllerBase(Mode, KeyListener, MouseListener, CommandListener): """Base of Controllers""" def __init__(self, engine, @@ -42,7 +42,7 @@ KeyListener.__init__(self, application.event_listener) MouseListener.__init__(self, application.event_listener) CommandListener.__init__(self, application.event_listener) - FifeMode.__init__(self) + Mode.__init__(self) self.engine = engine self.event_manager = engine.getEventManager() self.view = view diff -r ab6a0fd1668a -r 2a12e2843984 entities/general.py --- a/entities/general.py Sat Mar 24 09:59:46 2012 +0100 +++ b/entities/general.py Tue Mar 27 13:41:20 2012 +0200 @@ -11,7 +11,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from parpg.bGrease import Entity +from bGrease import Entity class General(Entity): diff -r ab6a0fd1668a -r 2a12e2843984 gamemodel.py --- a/gamemodel.py Sat Mar 24 09:59:46 2012 +0100 +++ b/gamemodel.py Tue Mar 27 13:41:20 2012 +0200 @@ -21,7 +21,7 @@ from fife import fife from fife.extensions.serializers.xmlobject import XMLObjectLoader -from parpg.bGrease.geometry import Vec2d +from bGrease.geometry import Vec2d from serializers import XmlSerializer from parpg import vfs diff -r ab6a0fd1668a -r 2a12e2843984 gamescenecontroller.py --- a/gamescenecontroller.py Sat Mar 24 09:59:46 2012 +0100 +++ b/gamescenecontroller.py Tue Mar 27 13:41:20 2012 +0200 @@ -35,7 +35,7 @@ ExamineContentsAction, ) -from parpg.world import World +from parpg.world import PARPGWorld #For debugging/code analysis if False: @@ -46,7 +46,7 @@ logger = logging.getLogger('gamescenecontroller') -class GameSceneController(World, ControllerBase): +class GameSceneController(PARPGWorld, ControllerBase): ''' This controller handles inputs when the game is in "scene" state. "Scene" state is when the player can move around and interact @@ -73,7 +73,7 @@ view, model, application) - World.__init__(self) + PARPGWorld.__init__(self) #setup functions for the GameEnvironment createItemByID = lambda identifier : ( @@ -590,7 +590,7 @@ if self.paused: return ControllerBase.pump(self, dt) - World.pump(self, dt) + PARPGWorld.pump(self, dt) self.updateMouse() if self.model.active_map: self.view.highlightFrontObject(self.last_mousecoords) diff -r ab6a0fd1668a -r 2a12e2843984 mode.py --- a/mode.py Sat Mar 24 09:59:46 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ - -from parpg.bGrease.mode import * -import abc - -class FifeManager(BaseManager): - - def __init__(self): - self.modes = [] - - def _pump(self): - if self.current_mode: - self.current_mode.pump(self.current_mode.engine.getTimeManager().getTimeDelta() / 1000.0) - -class FifeMode(BaseMode): - - def __init__(self): - BaseMode.__init__(self) - - @abc.abstractmethod - def pump(self, dt): - """Performs actions every frame""" \ No newline at end of file diff -r ab6a0fd1668a -r 2a12e2843984 systems/gamerulessystem.py --- a/systems/gamerulessystem.py Sat Mar 24 09:59:46 2012 +0100 +++ b/systems/gamerulessystem.py Tue Mar 27 13:41:20 2012 +0200 @@ -11,7 +11,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from parpg.bGrease import System +from bGrease import System class GameRulesSystem(System): """ diff -r ab6a0fd1668a -r 2a12e2843984 systems/scriptingsystem.py --- a/systems/scriptingsystem.py Sat Mar 24 09:59:46 2012 +0100 +++ b/systems/scriptingsystem.py Tue Mar 27 13:41:20 2012 +0200 @@ -14,7 +14,7 @@ from collections import deque from copy import deepcopy -from parpg.bGrease import System +from bGrease import System class Script(object): """Script object""" diff -r ab6a0fd1668a -r 2a12e2843984 world.py --- a/world.py Sat Mar 24 09:59:46 2012 +0100 +++ b/world.py Tue Mar 27 13:41:20 2012 +0200 @@ -1,28 +1,19 @@ -from parpg.bGrease.world import * -from parpg.bGrease.component import Component +from bGrease.grease_fife.world import World +from bGrease.component import Component -from parpg.mode import FifeMode +from bGrease.grease_fife.mode import Mode from parpg import components from parpg.components.fifeagent import commands from parpg.systems import ScriptingSystem from parpg.entities.action import ACTIONS -class World(FifeMode, BaseWorld): +class PARPGWorld(World): def __init__(self): - FifeMode.__init__(self) - BaseWorld.__init__(self) + World.__init__(self) def configure(self): """Configure the game world's components, systems and renderers""" for name, component in components.components.iteritems(): setattr(self.components, name, component) self.systems.scripting = ScriptingSystem(commands, ACTIONS) - - def pump(self, dt): - for component in self.components: - if hasattr(component, "step"): - component.step(dt) - for system in self.systems: - if hasattr(system, "step"): - system.step(dt) \ No newline at end of file