def centrality(self, offset): # return 0.0 offset = Point( abs(offset.x), offset.y + self.size.y - golden_ratio * 2.0 * self.size.y) if offset.y < 0.0: y_length = golden_ratio * 2.0 * self.size.y if offset.x < golden_ratio * self.size.x: return max(((self.size - abs(offset)) / self.size).lower(), 0.0) return max((y_length + offset.y) / y_length, 0.0) else: offset = Point(offset.x - golden_ratio * self.size.x, -offset.y) x_length = (1.0 - golden_ratio) * self.size.x linear = min( max(offset.y - offset.x + x_length, 0.0) / (x_length + y_length), 1.0) size = Point((1.0 - linear) * x_length, linear * y_length) return max(((size - offset) / size).lower(), 0.0) else: y_length = (1.0 - golden_ratio) * 2.0 * self.size.y linear = min( max(offset.y - offset.x + self.size.x, 0.0) / (self.size.x + y_length), 1.0) size = Point((1.0 - linear) * self.size.x, linear * y_length) return max(((size - offset) / size).lower(), 0.0)
def polygon(self): x_size = golden_ratio * self.size.x y_size = golden_ratio * 2.0 * self.size.y - self.size.y return (Point(-x_size, -self.size.y), Point(x_size, -self.size.y), Point(self.size.x, y_size), Point(0.0, self.size.y), Point(-self.size.x, y_size))
def centrality(self, offset): if offset.y < -self.size.y: return 0.0 else: x = abs(offset.x) / ((1.0 - (offset.y + self.size.y) / (2.0 * self.size.y)) * self.size.x) if offset.y < -self.size.y / 3.0: offset = Point(abs(offset.x), -offset.y - self.size.y / 3.0) frac_x = max(offset.x / self.size.x, 2.0 / 3.0) # x = abs(offset.x) / ((1.0 - (offset.y + self.size.y) / (2.0 * self.size.y)) * self.size.x) y = min(offset.y / (frac_x * 2.0 * self.size.y / 3.0), 1.0) linear = offset.y / (self.size.y * 2.0 / 3.0) linear **= 2 # linear = 1.0 - (1.0 - linear) ** 2 # linear = 1.0 else: offset = Point(abs(offset.x), offset.y + self.size.y / 3.0) frac_x = (offset.x / self.size.x) * 3.0 / 2.0 assert 1.0 - frac_x >= offset.y / (4.0 * self.size.y / 3.0) y = offset.y / ((1.0 - frac_x) * 4.0 * self.size.y / 3.0) linear = offset.y / (self.size.y * 4.0 / 3.0) linear = 1.0 - (1.0 - linear)**2 linear = 1.0 - (1.0 - linear)**2 assert 0.0 <= x <= 1.0, x assert 0.0 <= y <= 1.0, y assert 0.0 <= linear <= 1.0, linear # return ((1.0 - y) + (1.0 - x)) / 2.0 return linear * (1.0 - y) + (1.0 - linear) * (1.0 - x)
def distance(self, offset): offset = abs(offset) if offset.x > offset.y: size = Point(self.size.x, self.size.y / 3.0) else: size = Point(self.size.x / 3.0, self.size.y) return (offset - size).positive().length()
def from_model(model): if model['name'] in Shape.shapes: return Shape.shapes[model['name']]( size=Point.from_model(model['size'])) else: return Shape.shapes.get(model['name'], CustomRectangle)( size=Point.from_model(model['size']), init_name=model['name'])
def distance(self, offset): if offset.y < -self.size.y: return (abs(offset) - self.size).positive().length() else: offset = Point(abs(offset.x), offset.y + self.size.y) linear = min(max(offset.y - offset.x + self.size.x, 0.0) / (self.size.x + 2.0 * self.size.y), 1.0) size = Point((1.0 - linear) * self.size.x, linear * 2.0 * self.size.y) return (offset - size).positive().length()
def centrality(self, offset): offset = abs(offset) if offset.x > self.size.x / 3.0: size = Point(self.size.x, self.size.y / 3.0) elif offset.y > self.size.y / 3.0: size = Point(self.size.x / 3.0, self.size.y) elif offset.x > offset.y: size = Point(self.size.x / 3.0 * 4.0, self.size.y / 3.0) else: size = Point(self.size.x / 3.0, self.size.y / 3.0 * 4.0) return max(((size - offset) / size).lower(), 0.0)
def not_collides(self, other, ratio=False, symmetric=False, resolution=None): if resolution is None: resolution = default_resolution topleft1 = self.topleft bottomright1 = self.bottomright topleft2 = other.topleft bottomright2 = other.bottomright if topleft1.x < topleft2.x and topleft1.y < topleft2.y and bottomright1.x > bottomright2.x and bottomright1.y > bottomright2.y: if not ratio: return False elif symmetric: return 0.0 else: return (0.0, 0.0) elif bottomright1.distance(topleft1) < bottomright2.distance(topleft2): topleft, bottomright = topleft1, bottomright1 else: topleft, bottomright = topleft2, bottomright2 topleft *= resolution bottomright *= resolution average_resolution = 0.5 * (resolution.x + resolution.y) if ratio: granularity = 1.0 / resolution.x / resolution.y collision = 0.0 for _, point in Point.range(topleft, bottomright, resolution): distance1 = min( average_resolution * self.distance(point - self.center), 1.0) distance2 = max( 1.0 - average_resolution * other.distance(point - other.center), 0.0) average_distance = 0.5 * (distance1 + distance2) if average_distance > 0.95: collision += granularity * average_distance if symmetric: return min(collision / self.shape.area, collision / other.shape.area) else: return (collision / self.shape.area, collision / other.shape.area) else: min_distance = 1.0 / average_resolution for c, point in Point.range(topleft, bottomright, resolution): if (self.distance(point - self.center) > min_distance) and ( other.distance(point - other.center) <= min_distance): return True
def test(): for shape in Shape.get_shapes(): shape_cls = Shape.get_shape(name=shape) shape_obj = shape_cls.random_instance(size_range=(1.0, 1.0), distortion_range=(2.0, 2.0)) contains = 0 for point in Point.range(start=Point(-50, -50), end=Point(50, 50)): if point / 100 in shape_obj: contains += 1 estimated_area = contains / 10000 assert shape_obj.area <= 1.0, (shape, shape_obj.area) assert shape_obj.area == shape_cls.relative_area() or shape_obj.area == shape_cls.relative_area() / empirical_distortion_multiplier, (shape, shape_obj.area, shape_cls.relative_area()) assert abs(shape_obj.area - estimated_area) < 0.011, (shape, abs(shape_obj.area - estimated_area)) return True
def random_location(self, provoke_collision=False, min_distance=0.75): if isinstance(provoke_collision, Entity): entity = provoke_collision provoke_collision = True elif provoke_collision and len(self.entities) > 0: entity = choice(self.entities) else: provoke_collision = False if provoke_collision: angle = Point.from_angle(angle=random()) return entity.center + angle * entity.shape.size * (min_distance + random()) else: return Point.random_instance(Point.zero, Point.one)
def draw(self, world_array, world_size, draw_fn=None): shift = Point(2.0 / world_size.x, 2.0 / world_size.y) scale = 1.0 + 2.0 * shift topleft = (((self.topleft) / scale) * world_size).max(Point.izero) # + 0.5 * shift bottomright = (((self.bottomright + 2.0 * shift) / scale) * world_size).min(world_size) if draw_fn is None: color = self.color.get_color() world_length = min(*world_size) for (x, y), point in Point.range(topleft, bottomright, world_size): point = point * scale - shift offset = point - self.center distance = self.distance(offset) if distance == 0.0: # assert offset in self world_array[y, x] = self.texture.get_color(color, offset) else: # assert offset not in self distance = max(1.0 - distance * world_length, 0.0) world_array[y, x] = distance * self.texture.get_color(color, offset) + (1.0 - distance) * world_array[y, x] # if distance == 0.0: # centrality = self.centrality(offset) # world_array[y, x] = (centrality, centrality, centrality) + (1.0 - centrality) * self.texture.get_color(color, offset) # else: # assert self.centrality(offset) == 0.0 else: for (x, y), point in Point.range(topleft, bottomright, world_size): point = point * scale - shift offset = point - self.center world_array[y, x] = draw_fn(value=world_array[y, x], entity=self, offset=offset) bounding_box = False if bounding_box: # draw bounding box assert draw_fn is None x1 = world_size + 1 x2 = -1 y1 = world_size + 1 y2 = -1 for (x, y), point in Point.range(topleft, bottomright, world_size): if x < x1: x1 = x if x > x2: x2 = x if y < y1: y1 = y if y > y2: y2 = y for (x, y), point in Point.range(topleft, bottomright, world_size): if x == x1 or x == x2 or y == y1 or y == y2: world_array[y, x] = color
def polygon(self): curve_size = (1.0 - cos45) * self.size.x return (Point(-self.size.x, -curve_size), Point(-curve_size, -self.size.y), Point(curve_size, -self.size.y), Point(self.size.x, -curve_size), Point(self.size.x, curve_size), Point(curve_size, self.size.y), Point(-curve_size, self.size.y), Point(-self.size.x, curve_size))
def polygon(self): return (Point(-self.size.x, -self.size.y / 3.0), Point(-self.size.x / 3.0, -self.size.y), Point(self.size.x / 3.0, -self.size.y), Point(self.size.x, -self.size.y / 3.0), Point(self.size.x, self.size.y / 3.0), Point(self.size.x / 3.0, self.size.y), Point(-self.size.x / 3.0, self.size.y), Point(-self.size.x, self.size.y / 3.0))
def from_model(model): entity = Entity(shape=Shape.from_model(model['shape']), color=Color.from_model(model['color']), texture=Texture.from_model(model['texture']), center=Point.from_model(model['center']), rotation=model['rotation']) entity.id = model['id'] return entity
def distance(self, offset): offset = Point(abs(offset.x), offset.y + self.size.y - golden_ratio * 2.0 * self.size.y) if offset.y < 0.0: y_length = golden_ratio * 2.0 * self.size.y if offset.x < golden_ratio * self.size.x: return max(-offset.y - y_length, 0.0) else: offset = Point(offset.x - golden_ratio * self.size.x, -offset.y) x_length = (1.0 - golden_ratio) * self.size.x linear = min(max(offset.y - offset.x + x_length, 0.0) / (x_length + y_length), 1.0) size = Point((1.0 - linear) * x_length, linear * y_length) return (offset - size).positive().length() else: y_length = (1.0 - golden_ratio) * 2.0 * self.size.y linear = min(max(offset.y - offset.x + self.size.x, 0.0) / (self.size.x + y_length), 1.0) size = Point((1.0 - linear) * self.size.x, linear * y_length) return (offset - size).positive().length()
def random_instance(size_range, distortion_range): distortion = uniform(*distortion_range) if distortion_range[0] < distortion_range[1]: distortion_ratio = (distortion - distortion_range[0]) / (distortion_range[1] - distortion_range[0]) min_size = size_range[0] + (size_range[1] - size_range[0]) * distortion_ratio * 0.5 size = quadratic_uniform(min_size, size_range[1]) else: size = quadratic_uniform(*size_range) return Ellipse(Point(size, size / distortion))
def __init__(self, size, color): assert isinstance(size, int) and size > 0 assert isinstance(color, str) and color in Color.colors super(World, self).__init__(WorldShape(), Color(color, Color.get_rgb(color), 0.0), Texture.get_texture('solid')(), Point.half, 0.0) self.relative_topleft = -Point.half self.relative_bottomright = Point.half self.topleft = Point.zero self.bottomright = Point.one self.size = Point(size, size) self.entities = [] self.meta = dict()
def from_model(model): return Shape.shapes[model['name']]( size=Point.from_model(model['size']))
def collides(self, other, ratio=False, symmetric=False, resolution=None): if other.id == self.id: if not ratio: return True elif symmetric: return 1.0 else: return (1.0, 1.0) if other.id in self.collisions and self.id in other.collisions: if not ratio: return min(self.collisions[other.id], other.collisions[self.id]) > 0.0 elif symmetric: return min(self.collisions[other.id], other.collisions[self.id]) else: return (self.collisions[other.id], other.collisions[self.id]) topleft1 = self.topleft bottomright1 = self.bottomright topleft2 = other.topleft bottomright2 = other.bottomright if bottomright1.x < topleft2.x or topleft1.x > bottomright2.x or bottomright1.y < topleft2.y or topleft1.y > bottomright2.y: if other.id is not None: self.collisions[other.id] = 0.0 if self.id is not None: other.collisions[self.id] = 0.0 if not ratio: return False elif symmetric: return 0.0 else: return (0.0, 0.0) else: topleft, bottomright = topleft1.max(topleft2), bottomright1.min(bottomright2) if resolution is None: resolution = default_resolution topleft *= resolution bottomright *= resolution average_resolution = 0.5 * (resolution.x + resolution.y) if ratio: granularity = 1.0 / resolution.x / resolution.y collision = 0.0 for _, point in Point.range(topleft, bottomright, resolution): distance1 = max(1.0 - average_resolution * self.distance(point - self.center), 0.0) distance2 = max(1.0 - average_resolution * other.distance(point - other.center), 0.0) average_distance = 0.5 * (distance1 + distance2) if average_distance > 0.95: collision += granularity * average_distance collision1 = collision / self.shape.area collision2 = collision / other.shape.area if other.id is not None: self.collisions[other.id] = collision1 if self.id is not None: other.collisions[self.id] = collision2 if symmetric: return min(collision1, collision2) else: return (collision1, collision2) else: min_distance = 1.0 / average_resolution for _, point in Point.range(topleft, bottomright, resolution): if (self.distance(point - self.center) <= min_distance) and (other.distance(point - other.center) <= min_distance): return True
from __future__ import division from math import cos, pi, sin from random import choice, random from shapeworld.world import Point, Shape, Color, Texture default_resolution = Point(100, 100) class Entity(object): __slots__ = ('id', 'shape', 'color', 'texture', 'center', 'rotation', 'rotation_sin', 'rotation_cos', 'relative_topleft', 'relative_bottomright', 'topleft', 'bottomright', 'collisions') def __init__(self, shape, color, texture, center, rotation): assert isinstance(shape, Shape) assert isinstance(color, Color) assert isinstance(texture, Texture) assert isinstance(center, Point) assert isinstance(rotation, float) and 0.0 <= rotation < 1.0 self.id = None self.shape = shape self.color = color self.texture = texture self.rotation = rotation self.rotation_sin = sin(-rotation * 2.0 * pi) self.rotation_cos = cos(-rotation * 2.0 * pi) self.set_center(center=center) self.collisions = dict() def __hash__(self): assert self.id is not None
def polygon(self): return (Point(-self.size.x, -self.size.y), Point(self.size.x, -self.size.y), Point(-self.size.x, self.size.y), Point(self.size.x, self.size.y))
def centrality(self, offset): offset += Point(0.0, self.size.y) if offset.y < 0.0: return 0.0 else: return max((self.size.x - offset.length()) / self.size.x, 0.0)
def distance(self, offset): offset += Point(0.0, self.size.y) if offset.y < 0.0: return (abs(offset) - Point(self.size.x, 0.0)).positive().length() else: return max(offset.length() - self.size.x, 0.0)
def __init__(self, size): if isinstance(size, float): size = Point(size, size * 0.5) return super(Semicircle, self).__init__(size=size)
def __init__(self, size): if isinstance(size, float): size = Point(size, size * sqrt34) return super(Triangle, self).__init__(size=size)
def __init__(self, size): if isinstance(size, float): size = Point(size, size) return super(Cross, self).__init__(size=size)
def __contains__(self, offset): offset += Point(0.0, self.size.y) return offset.length() <= self.size.x and offset.y >= 0.0
def size(self): return Point(0.5, 0.5)
def __init__(self, size): if isinstance(size, float): size = Point(size, size * cos18) return super(Pentagon, self).__init__(size=size)