mirror of
https://github.com/illiumst/marl-factory-grid.git
synced 2025-05-23 15:26:43 +02:00
290 lines
11 KiB
Python
290 lines
11 KiB
Python
"""This file is based on the car_dynamics.py in OpenAI's gym.
|
|
|
|
Description
|
|
We provide some modified CarRacing environments here:
|
|
* CarRacingColor-v0
|
|
Both the lane and the grass colors are perturbed at the env.reset() with scalar noises.
|
|
* CarRacingColor3-v0
|
|
Both the lane and the grass colors are perturbed at the env.reset() with 3d noises.
|
|
* CarRacingBar-v0
|
|
We add vertical bars on the left and right side of the screen.
|
|
* CarRacingBlob-v0
|
|
A red blob that follows the car at a fixed position in the car's frame.
|
|
* CarRacingNoise-v0
|
|
We replace the green background with noise.
|
|
* CarRacingVideo-v0
|
|
We replace the green background with frames from a video, the user is
|
|
responsible for creating these frames and pass the directory.
|
|
These environments were first used in the paper "Neuroevolution of Self-Interpretable Agent"
|
|
https://attentionagent.github.io/
|
|
|
|
Author
|
|
Yujin Tang (yujintang@google.com)
|
|
"""
|
|
|
|
import numpy as np
|
|
import math
|
|
import Box2D
|
|
from Box2D.b2 import (edgeShape, circleShape, fixtureDef, polygonShape, revoluteJointDef, contactListener, shape)
|
|
|
|
# Top-down car dynamics simulation.
|
|
#
|
|
# Some ideas are taken from this great tutorial http://www.iforce2d.net/b2dtut/top-down-car by Chris Campbell.
|
|
# This simulation is a bit more detailed, with wheels rotation.
|
|
#
|
|
# Created by Oleg Klimov. Licensed on the same terms as the rest of OpenAI Gym.
|
|
|
|
SIZE = 0.02
|
|
ENGINE_POWER = 100000000*SIZE*SIZE
|
|
WHEEL_MOMENT_OF_INERTIA = 4000*SIZE*SIZE
|
|
FRICTION_LIMIT = 1000000*SIZE*SIZE # friction ~= mass ~= size^2 (calculated implicitly using density)
|
|
WHEEL_R = 27
|
|
WHEEL_W = 14
|
|
WHEELPOS = [
|
|
(-55,+80), (+55,+80),
|
|
(-55,-82), (+55,-82)
|
|
]
|
|
HULL_POLY1 =[
|
|
(-60,+130), (+60,+130),
|
|
(+60,+110), (-60,+110)
|
|
]
|
|
HULL_POLY2 =[
|
|
(-15,+120), (+15,+120),
|
|
(+20, +20), (-20, 20)
|
|
]
|
|
HULL_POLY3 =[
|
|
(+25, +20),
|
|
(+50, -10),
|
|
(+50, -40),
|
|
(+20, -90),
|
|
(-20, -90),
|
|
(-50, -40),
|
|
(-50, -10),
|
|
(-25, +20)
|
|
]
|
|
HULL_POLY4 =[
|
|
(-50,-120), (+50,-120),
|
|
(+50,-90), (-50,-90)
|
|
]
|
|
|
|
WHEEL_COLOR = (0.0,0.0,0.0)
|
|
WHEEL_WHITE = (0.3,0.3,0.3)
|
|
MUD_COLOR = (0.4,0.4,0.0)
|
|
|
|
class Car:
|
|
def __init__(self, world, init_angle, init_x, init_y, add_blob=False):
|
|
self.world = world
|
|
|
|
self.add_blob = add_blob
|
|
|
|
fixtures = [
|
|
fixtureDef(shape=polygonShape(
|
|
vertices=[ (x*SIZE,y*SIZE) for x,y in HULL_POLY1 ]), density=1.0),
|
|
fixtureDef(shape=polygonShape(
|
|
vertices=[ (x*SIZE,y*SIZE) for x,y in HULL_POLY2 ]), density=1.0),
|
|
fixtureDef(shape=polygonShape(
|
|
vertices=[ (x*SIZE,y*SIZE) for x,y in HULL_POLY3 ]), density=1.0),
|
|
fixtureDef(shape=polygonShape(
|
|
vertices=[ (x*SIZE,y*SIZE) for x,y in HULL_POLY4 ]), density=1.0),
|
|
]
|
|
self.hull = self.world.CreateDynamicBody(
|
|
position = (init_x, init_y),
|
|
angle = init_angle,
|
|
fixtures = fixtures,
|
|
)
|
|
self.hull.color = (0.8,0.0,0.0)
|
|
self.wheels = []
|
|
self.fuel_spent = 0.0
|
|
WHEEL_POLY = [
|
|
(-WHEEL_W,+WHEEL_R), (+WHEEL_W,+WHEEL_R),
|
|
(+WHEEL_W,-WHEEL_R), (-WHEEL_W,-WHEEL_R)
|
|
]
|
|
for wx,wy in WHEELPOS:
|
|
front_k = 1.0 if wy > 0 else 1.0
|
|
w = self.world.CreateDynamicBody(
|
|
position = (init_x+wx*SIZE, init_y+wy*SIZE),
|
|
angle = init_angle,
|
|
fixtures = fixtureDef(
|
|
shape=polygonShape(vertices=[ (x*front_k*SIZE,y*front_k*SIZE) for x,y in WHEEL_POLY ]),
|
|
density=0.1,
|
|
categoryBits=0x0020,
|
|
maskBits=0x001,
|
|
restitution=0.0)
|
|
)
|
|
w.wheel_rad = front_k*WHEEL_R*SIZE
|
|
w.color = WHEEL_COLOR
|
|
w.gas = 0.0
|
|
w.brake = 0.0
|
|
w.steer = 0.0
|
|
w.phase = 0.0 # wheel angle
|
|
w.omega = 0.0 # angular velocity
|
|
w.skid_start = None
|
|
w.skid_particle = None
|
|
rjd = revoluteJointDef(
|
|
bodyA=self.hull,
|
|
bodyB=w,
|
|
localAnchorA=(wx*SIZE,wy*SIZE),
|
|
localAnchorB=(0,0),
|
|
enableMotor=True,
|
|
enableLimit=True,
|
|
maxMotorTorque=180*900*SIZE*SIZE,
|
|
motorSpeed = 0,
|
|
lowerAngle = -0.4,
|
|
upperAngle = +0.4,
|
|
)
|
|
w.joint = self.world.CreateJoint(rjd)
|
|
w.tiles = set()
|
|
w.userData = w
|
|
self.wheels.append(w)
|
|
self.drawlist = self.wheels + [self.hull]
|
|
self.particles = []
|
|
|
|
def gas(self, gas):
|
|
'control: rear wheel drive'
|
|
gas = np.clip(gas, 0, 1)
|
|
for w in self.wheels[2:4]:
|
|
diff = gas - w.gas
|
|
if diff > 0.1: diff = 0.1 # gradually increase, but stop immediately
|
|
w.gas += diff
|
|
|
|
def brake(self, b):
|
|
'control: brake b=0..1, more than 0.9 blocks wheels to zero rotation'
|
|
for w in self.wheels:
|
|
w.brake = b
|
|
|
|
def steer(self, s):
|
|
'control: steer s=-1..1, it takes time to rotate steering wheel from side to side, s is target position'
|
|
self.wheels[0].steer = s
|
|
self.wheels[1].steer = s
|
|
|
|
def step(self, dt):
|
|
for w in self.wheels:
|
|
# Steer each wheel
|
|
dir = np.sign(w.steer - w.joint.angle)
|
|
val = abs(w.steer - w.joint.angle)
|
|
w.joint.motorSpeed = dir*min(50.0*val, 3.0)
|
|
|
|
# Position => friction_limit
|
|
grass = True
|
|
friction_limit = FRICTION_LIMIT*0.6 # Grass friction if no tile
|
|
for tile in w.tiles:
|
|
friction_limit = max(friction_limit, FRICTION_LIMIT*tile.road_friction)
|
|
grass = False
|
|
|
|
# Force
|
|
forw = w.GetWorldVector( (0,1) )
|
|
side = w.GetWorldVector( (1,0) )
|
|
v = w.linearVelocity
|
|
vf = forw[0]*v[0] + forw[1]*v[1] # forward speed
|
|
vs = side[0]*v[0] + side[1]*v[1] # side speed
|
|
|
|
# WHEEL_MOMENT_OF_INERTIA*np.square(w.omega)/2 = E -- energy
|
|
# WHEEL_MOMENT_OF_INERTIA*w.omega * domega/dt = dE/dt = W -- power
|
|
# domega = dt*W/WHEEL_MOMENT_OF_INERTIA/w.omega
|
|
w.omega += dt*ENGINE_POWER*w.gas/WHEEL_MOMENT_OF_INERTIA/(abs(w.omega)+5.0) # small coef not to divide by zero
|
|
self.fuel_spent += dt*ENGINE_POWER*w.gas
|
|
|
|
if w.brake >= 0.9:
|
|
w.omega = 0
|
|
elif w.brake > 0:
|
|
BRAKE_FORCE = 15 # radians per second
|
|
dir = -np.sign(w.omega)
|
|
val = BRAKE_FORCE*w.brake
|
|
if abs(val) > abs(w.omega): val = abs(w.omega) # low speed => same as = 0
|
|
w.omega += dir*val
|
|
w.phase += w.omega*dt
|
|
|
|
vr = w.omega*w.wheel_rad # rotating wheel speed
|
|
f_force = -vf + vr # force direction is direction of speed difference
|
|
p_force = -vs
|
|
|
|
# Physically correct is to always apply friction_limit until speed is equal.
|
|
# But dt is finite, that will lead to oscillations if difference is already near zero.
|
|
f_force *= 205000*SIZE*SIZE # Random coefficient to cut oscillations in few steps (have no effect on friction_limit)
|
|
p_force *= 205000*SIZE*SIZE
|
|
force = np.sqrt(np.square(f_force) + np.square(p_force))
|
|
|
|
# Skid trace
|
|
if abs(force) > 2.0*friction_limit:
|
|
if w.skid_particle and w.skid_particle.grass==grass and len(w.skid_particle.poly) < 30:
|
|
w.skid_particle.poly.append( (w.position[0], w.position[1]) )
|
|
elif w.skid_start is None:
|
|
w.skid_start = w.position
|
|
else:
|
|
w.skid_particle = self._create_particle( w.skid_start, w.position, grass )
|
|
w.skid_start = None
|
|
else:
|
|
w.skid_start = None
|
|
w.skid_particle = None
|
|
|
|
if abs(force) > friction_limit:
|
|
f_force /= force
|
|
p_force /= force
|
|
force = friction_limit # Correct physics here
|
|
f_force *= force
|
|
p_force *= force
|
|
|
|
w.omega -= dt*f_force*w.wheel_rad/WHEEL_MOMENT_OF_INERTIA
|
|
|
|
w.ApplyForceToCenter( (
|
|
p_force*side[0] + f_force*forw[0],
|
|
p_force*side[1] + f_force*forw[1]), True )
|
|
|
|
def draw(self, viewer, draw_particles=True):
|
|
if draw_particles:
|
|
for p in self.particles:
|
|
viewer.draw_polyline(p.poly, color=p.color, linewidth=5)
|
|
for obj in self.drawlist:
|
|
for i, f in enumerate(obj.fixtures):
|
|
trans = f.body.transform
|
|
path = [trans*v for v in f.shape.vertices]
|
|
if self.add_blob and i == 3:
|
|
obj_color = (0.8, 0.0, 0.)
|
|
offset_x = f.shape.vertices[0][0] + 20
|
|
offset_y = f.shape.vertices[0][1] + 10
|
|
width = height = 8
|
|
blob =[
|
|
trans * (+offset_x, +offset_y+height),
|
|
trans * (+offset_x+width, +offset_y+height),
|
|
trans * (+offset_x+width, +offset_y),
|
|
trans * (+offset_x, +offset_y),
|
|
]
|
|
viewer.draw_polygon(blob, color=obj_color)
|
|
viewer.draw_polygon(path, color=obj.color)
|
|
if "phase" not in obj.__dict__: continue
|
|
a1 = obj.phase
|
|
a2 = obj.phase + 1.2 # radians
|
|
s1 = math.sin(a1)
|
|
s2 = math.sin(a2)
|
|
c1 = math.cos(a1)
|
|
c2 = math.cos(a2)
|
|
if s1>0 and s2>0: continue
|
|
if s1>0: c1 = np.sign(c1)
|
|
if s2>0: c2 = np.sign(c2)
|
|
white_poly = [
|
|
(-WHEEL_W*SIZE, +WHEEL_R*c1*SIZE), (+WHEEL_W*SIZE, +WHEEL_R*c1*SIZE),
|
|
(+WHEEL_W*SIZE, +WHEEL_R*c2*SIZE), (-WHEEL_W*SIZE, +WHEEL_R*c2*SIZE)
|
|
]
|
|
viewer.draw_polygon([trans*v for v in white_poly], color=WHEEL_WHITE)
|
|
|
|
def _create_particle(self, point1, point2, grass):
|
|
class Particle:
|
|
pass
|
|
p = Particle()
|
|
p.color = WHEEL_COLOR if not grass else MUD_COLOR
|
|
p.ttl = 1
|
|
p.poly = [(point1[0],point1[1]), (point2[0],point2[1])]
|
|
p.grass = grass
|
|
self.particles.append(p)
|
|
while len(self.particles) > 30:
|
|
self.particles.pop(0)
|
|
return p
|
|
|
|
def destroy(self):
|
|
self.world.DestroyBody(self.hull)
|
|
self.hull = None
|
|
for w in self.wheels:
|
|
self.world.DestroyBody(w)
|
|
self.wheels = []
|
|
|