diff options
Diffstat (limited to 'murl/world')
-rw-r--r-- | murl/world/__init__.py | 15 | ||||
-rw-r--r-- | murl/world/simple.py | 77 |
2 files changed, 92 insertions, 0 deletions
diff --git a/murl/world/__init__.py b/murl/world/__init__.py new file mode 100644 index 0000000..254a0f6 --- /dev/null +++ b/murl/world/__init__.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod + +class World(ABC): + ROBOT_RADIUS = 0.105 / 2. + @abstractmethod + def is_valid(self, pose): + raise NotImplementedError + + @abstractmethod + def get_dist(self, sources, angles): + raise NotImplementedError + + @abstractmethod + def sample(self, particles): + raise NotImplementedError diff --git a/murl/world/simple.py b/murl/world/simple.py new file mode 100644 index 0000000..195c4db --- /dev/null +++ b/murl/world/simple.py @@ -0,0 +1,77 @@ +import numpy as np + +from murl.world import World + +WALL_OFFSET = 2. +CYLINDER_POSITION = np.array([.3, .2], dtype=np.float32) +CYLINDER_OFFSET = .3 + +SEGMENTS = list(map(lambda ps: (np.array(ps[0]), np.array(ps[1]), ps[2]), + [[[-WALL_OFFSET, -WALL_OFFSET], [0, 1], 2*WALL_OFFSET], + [[-WALL_OFFSET, WALL_OFFSET], [1, 0], 2*WALL_OFFSET], + [[WALL_OFFSET, WALL_OFFSET], [0, -1], 2*WALL_OFFSET], + [[WALL_OFFSET, -WALL_OFFSET], [-1, 0], 2*WALL_OFFSET]])) + +class SimpleWorld(World): + WALL_RADIUS = WALL_OFFSET - World.ROBOT_RADIUS + CYLINDER_RADIUS = CYLINDER_OFFSET + World.ROBOT_RADIUS + + def __init__(self, rng): + self._rng = rng + + def is_valid(self, poses): + return ((-self.WALL_RADIUS < poses[...,0]) + & (poses[...,0] < self.WALL_RADIUS) + & (-self.WALL_RADIUS < poses[...,1]) + & (poses[...,1] < self.WALL_RADIUS) + & (np.sum((poses[...,:2] - CYLINDER_POSITION) ** 2, axis=-1) > self.CYLINDER_RADIUS ** 2)) + + def get_dist(self, sources, angles): + def dot(x, y): + return x[...,0] * y[...,0] + x[...,1] * y[...,1] + def cross(x, y): + return x[...,0] * y[...,1] - x[...,1] * y[...,0] + + def intersect_segment(sources, angle, start, direction, length): + Y = start - sources[:,:2] + r = np.stack((np.cos(angle + sources[:,2]), + np.sin(angle + sources[:,2]))).T + t = cross(Y, direction) / cross(r, direction) + t1 = cross(Y, r) / cross(r, direction) + + return np.where((t >= 0) & (t1 >= 0) & (t1 <= length), t, float('inf')) + + def intersect_cylinder(sources, angle, center, radius): + Y = center - sources[:,:2] + d = np.stack((np.cos(angle + sources[:,2]), + np.sin(angle + sources[:,2]))).T + b = dot(d, Y) + c = dot(Y, Y) - radius**2 + disc = b**2 - dot(Y, Y) + radius**2 + + mask = (disc > 0) & (b > 0) & (b ** 2 > disc) + + b[~mask] = float('inf') + b[mask] -= np.sqrt(disc[mask]) + + return b + + intersections = np.stack([np.stack( + (intersect_segment(sources, angle, *SEGMENTS[0]), + intersect_segment(sources, angle, *SEGMENTS[1]), + intersect_segment(sources, angle, *SEGMENTS[2]), + intersect_segment(sources, angle, *SEGMENTS[3]), + intersect_cylinder(sources, angle, CYLINDER_POSITION, CYLINDER_OFFSET)) + ) for angle in angles]) + + return np.amin(intersections, axis=(0,1)) + + def sample(self, particles): + particles[:,2] = self._rng.uniform(-np.pi, np.pi, size=len(particles)) + + invalid = np.ones(shape=len(particles), dtype=np.bool) + while np.any(invalid): + count = np.ma.array(particles[:,0], mask=~invalid).count() + particles[invalid,0] = self._rng.uniform(-self.WALL_RADIUS, self.WALL_RADIUS, size=count) + particles[invalid,1] = self._rng.uniform(-self.WALL_RADIUS, self.WALL_RADIUS, size=count) + invalid = ~self.is_valid(particles) |