tinysim 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinysim/__init__.py +11 -0
- tinysim/_tk_base.py +54 -0
- tinysim/flappy/__init__.py +103 -0
- tinysim/flappy/sim.js +118 -0
- tinysim/flappy/tk.py +94 -0
- tinysim/flappy/widget.py +53 -0
- tinysim/frogger/__init__.py +145 -0
- tinysim/frogger/sim.js +110 -0
- tinysim/frogger/tk.py +95 -0
- tinysim/frogger/widget.py +60 -0
- tinysim/mountain_car/__init__.py +56 -0
- tinysim/mountain_car/sim.js +158 -0
- tinysim/mountain_car/styles.css +5 -0
- tinysim/mountain_car/tk.py +141 -0
- tinysim/mountain_car/widget.py +56 -0
- tinysim/simple_amr/__init__.py +0 -0
- tinysim/simple_amr/example_maps.py +121 -0
- tinysim/simple_amr/sim.js +430 -0
- tinysim/simple_amr/styles.css +54 -0
- tinysim/simple_amr/widget.py +73 -0
- tinysim/topdown_driving/__init__.py +190 -0
- tinysim/topdown_driving/sim.js +136 -0
- tinysim/topdown_driving/tk.py +180 -0
- tinysim/topdown_driving/track_0.json +753 -0
- tinysim/topdown_driving/widget.py +60 -0
- tinysim-0.0.1.dist-info/METADATA +56 -0
- tinysim-0.0.1.dist-info/RECORD +33 -0
- tinysim-0.0.1.dist-info/WHEEL +5 -0
- tinysim-0.0.1.dist-info/top_level.txt +2 -0
- tinysim_warp/cart_pole/__init__.py +259 -0
- tinysim_warp/quadruped/__init__.py +203 -0
- tinysim_warp/simple_quadruped/__init__.py +168 -0
- tinysim_warp/simple_quadruped/simple_quadruped.urdf +247 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import math
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from .. import SimEnvironment
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
with open(Path(__file__).parent / "track_0.json", "r") as f:
|
|
10
|
+
track = json.load(f)
|
|
11
|
+
except FileNotFoundError:
|
|
12
|
+
raise FileNotFoundError("Unable to load track data.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MAX_VEL = 20.0
|
|
16
|
+
ACCELERATION = 8.0
|
|
17
|
+
VEL_FRICT = 2.0
|
|
18
|
+
TURN_SPEED = math.radians(100)
|
|
19
|
+
|
|
20
|
+
CAR_LENGTH = 1.6 # WORLD units (smaller)
|
|
21
|
+
CAR_WIDTH = 0.8
|
|
22
|
+
CAR_RADIUS = 0.5
|
|
23
|
+
|
|
24
|
+
LOCAL_WALLS = track["walls"]
|
|
25
|
+
CHECKPOINTS = np.array(track["checkpoints"], dtype=np.float32)
|
|
26
|
+
CHECKPOINT_RADIUS = 3.0
|
|
27
|
+
|
|
28
|
+
WORLD_WALLS = np.array(
|
|
29
|
+
[(x, y, w, h, math.radians(rot)) for x, y, w, h, rot in LOCAL_WALLS],
|
|
30
|
+
dtype=np.float32,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
W_X, W_Y, W_W, W_H, W_ROT = WORLD_WALLS.T
|
|
34
|
+
W_HW = W_W * 0.5
|
|
35
|
+
W_HH = W_H * 0.5
|
|
36
|
+
W_COS = np.cos(-W_ROT)
|
|
37
|
+
W_SIN = np.sin(-W_ROT)
|
|
38
|
+
|
|
39
|
+
# Precompute ray offsets
|
|
40
|
+
RAY_SPREAD = math.radians(75) # total fan angle
|
|
41
|
+
RAY_LENGTH = 12.0 # world units
|
|
42
|
+
RAY_COUNT = 5
|
|
43
|
+
ray_offsets = np.linspace(
|
|
44
|
+
-RAY_SPREAD * 0.5, RAY_SPREAD * 0.5, RAY_COUNT, dtype=np.float32
|
|
45
|
+
)
|
|
46
|
+
EPS = 1e-6
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cast_rays(x, y, angle):
|
|
50
|
+
# x,y,angle: (N,)
|
|
51
|
+
a = angle[:, None] + ray_offsets[None, :] # (N,R)
|
|
52
|
+
dx = np.cos(a)
|
|
53
|
+
dy = np.sin(a)
|
|
54
|
+
best_t = np.full((x.shape[0], RAY_COUNT), RAY_LENGTH, dtype=np.float32)
|
|
55
|
+
|
|
56
|
+
# Expand dims for broadcasting
|
|
57
|
+
ox = x[:, None, None]
|
|
58
|
+
oy = y[:, None, None]
|
|
59
|
+
rdx = dx[:, :, None]
|
|
60
|
+
rdy = dy[:, :, None]
|
|
61
|
+
|
|
62
|
+
wx = W_X[None, None, :]
|
|
63
|
+
wy = W_Y[None, None, :]
|
|
64
|
+
|
|
65
|
+
# transform ray to wall space
|
|
66
|
+
rox = (ox - wx) * W_COS - (oy - wy) * W_SIN
|
|
67
|
+
roy = (ox - wx) * W_SIN + (oy - wy) * W_COS
|
|
68
|
+
rdxl = rdx * W_COS - rdy * W_SIN
|
|
69
|
+
rdyl = rdx * W_SIN + rdy * W_COS
|
|
70
|
+
|
|
71
|
+
tmin = np.full_like(rox, -1e9)
|
|
72
|
+
tmax = np.full_like(rox, 1e9)
|
|
73
|
+
mask_x = np.abs(rdxl) > EPS
|
|
74
|
+
safe_rdxl = np.where(mask_x, rdxl, 1.0)
|
|
75
|
+
tx1 = (-W_HW - rox) / safe_rdxl
|
|
76
|
+
tx2 = (W_HW - rox) / safe_rdxl
|
|
77
|
+
tmin = np.where(mask_x, np.maximum(tmin, np.minimum(tx1, tx2)), tmin)
|
|
78
|
+
tmax = np.where(mask_x, np.minimum(tmax, np.maximum(tx1, tx2)), tmax)
|
|
79
|
+
mask_y = np.abs(rdyl) > EPS
|
|
80
|
+
safe_rdyl = np.where(mask_y, rdyl, 1.0)
|
|
81
|
+
ty1 = (-W_HH - roy) / safe_rdyl
|
|
82
|
+
ty2 = (W_HH - roy) / safe_rdyl
|
|
83
|
+
tmin = np.where(mask_y, np.maximum(tmin, np.minimum(ty1, ty2)), tmin)
|
|
84
|
+
tmax = np.where(mask_y, np.minimum(tmax, np.maximum(ty1, ty2)), tmax)
|
|
85
|
+
valid = (tmax >= tmin) & (tmax > 0.0)
|
|
86
|
+
t = np.where(valid, np.where(tmin > 0, tmin, tmax), np.inf)
|
|
87
|
+
best_t = np.minimum(best_t, t.min(axis=2))
|
|
88
|
+
return best_t
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def collides(cx, cy):
|
|
92
|
+
dx = cx[:, None] - W_X[None, :]
|
|
93
|
+
dy = cy[:, None] - W_Y[None, :]
|
|
94
|
+
lx = dx * W_COS[None, :] - dy * W_SIN[None, :]
|
|
95
|
+
ly = dx * W_SIN[None, :] + dy * W_COS[None, :]
|
|
96
|
+
px = np.clip(lx, -W_HW[None, :], W_HW[None, :])
|
|
97
|
+
py = np.clip(ly, -W_HH[None, :], W_HH[None, :])
|
|
98
|
+
ddx = lx - px
|
|
99
|
+
ddy = ly - py
|
|
100
|
+
hit = (ddx**2 + ddy**2) <= CAR_RADIUS**2
|
|
101
|
+
return hit.any(axis=1)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TopDownDrivingEnv(SimEnvironment):
|
|
105
|
+
def __init__(self, num_envs: int = 1):
|
|
106
|
+
self.num_envs = num_envs
|
|
107
|
+
self.reset()
|
|
108
|
+
|
|
109
|
+
def reset(self):
|
|
110
|
+
self.x = np.full(self.num_envs, -85.0, dtype=np.float32)
|
|
111
|
+
self.y = np.full(self.num_envs, -42.0, dtype=np.float32)
|
|
112
|
+
self.angle = np.zeros(self.num_envs, dtype=np.float32)
|
|
113
|
+
self.velocity = np.zeros(self.num_envs, dtype=np.float32)
|
|
114
|
+
self.rays = []
|
|
115
|
+
|
|
116
|
+
self.checkpoint_idx = np.zeros(self.num_envs, dtype=np.int32)
|
|
117
|
+
cx, cy = CHECKPOINTS[0]
|
|
118
|
+
self.prev_dist = np.hypot(self.x - cx, self.y - cy)
|
|
119
|
+
return {
|
|
120
|
+
"x": self.x,
|
|
121
|
+
"y": self.y,
|
|
122
|
+
"angle": self.angle,
|
|
123
|
+
"velocity": self.velocity,
|
|
124
|
+
"rays": self.rays,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def step(self, action, dt=0.02):
|
|
128
|
+
throttle = action.get("throttle", 0.0)
|
|
129
|
+
steer = action.get("steer", 0.0)
|
|
130
|
+
|
|
131
|
+
if np.isscalar(throttle) and np.isscalar(steer):
|
|
132
|
+
throttle = np.full(self.num_envs, throttle, dtype=np.float32)
|
|
133
|
+
steer = np.full(self.num_envs, steer, dtype=np.float32)
|
|
134
|
+
elif isinstance(throttle, np.ndarray) and isinstance(steer, np.ndarray):
|
|
135
|
+
throttle = np.asarray(throttle, dtype=np.float32)
|
|
136
|
+
steer = np.asarray(steer, dtype=np.float32)
|
|
137
|
+
if throttle.shape[0] != self.num_envs or steer.shape[0] != self.num_envs:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"Expected actions of shape ({self.num_envs},), got {throttle.shape} and {steer.shape}"
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"Inputs throttle and steer must both be either scalars or numpy arrays."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.velocity += throttle * ACCELERATION * dt
|
|
147
|
+
self.velocity = np.clip(self.velocity, 0.0, MAX_VEL)
|
|
148
|
+
self.angle -= steer * TURN_SPEED * dt
|
|
149
|
+
|
|
150
|
+
dx = np.cos(self.angle) * self.velocity * dt
|
|
151
|
+
dy = np.sin(self.angle) * self.velocity * dt
|
|
152
|
+
nx, ny = self.x + dx, self.y + dy
|
|
153
|
+
|
|
154
|
+
hit = collides(nx, ny)
|
|
155
|
+
self.x = np.where(hit, self.x, nx)
|
|
156
|
+
self.y = np.where(hit, self.y, ny)
|
|
157
|
+
self.velocity = np.where(hit, 0.0, self.velocity)
|
|
158
|
+
|
|
159
|
+
mask = np.abs(throttle) < 1e-3
|
|
160
|
+
self.velocity = np.where(
|
|
161
|
+
mask, np.maximum(0.0, self.velocity - VEL_FRICT * dt), self.velocity
|
|
162
|
+
)
|
|
163
|
+
self.rays = cast_rays(self.x, self.y, self.angle)
|
|
164
|
+
|
|
165
|
+
# TODO: should reward be scaled by the distance between checkpoints?
|
|
166
|
+
# return done if the car hits a wall?
|
|
167
|
+
cp = CHECKPOINTS[self.checkpoint_idx]
|
|
168
|
+
dist = np.hypot(self.x - cp[:, 0], self.y - cp[:, 1])
|
|
169
|
+
|
|
170
|
+
# Reward is the delta to see if the car is getting closer to the checkpoint
|
|
171
|
+
reward = self.prev_dist - dist
|
|
172
|
+
reached = dist <= CHECKPOINT_RADIUS
|
|
173
|
+
|
|
174
|
+
bonus = 1.0
|
|
175
|
+
reward += np.where(reached, bonus, 0.0)
|
|
176
|
+
self.checkpoint_idx = np.where(
|
|
177
|
+
reached, self.checkpoint_idx + 1, self.checkpoint_idx
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
cp = CHECKPOINTS[self.checkpoint_idx]
|
|
181
|
+
self.prev_dist = np.hypot(self.x - cp[:, 0], self.y - cp[:, 1])
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
"x": self.x,
|
|
185
|
+
"y": self.y,
|
|
186
|
+
"angle": self.angle,
|
|
187
|
+
"velocity": self.velocity,
|
|
188
|
+
"rays": self.rays,
|
|
189
|
+
"reward": reward,
|
|
190
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
async render({ model, el }) {
|
|
3
|
+
const [CANVAS_W, CANVAS_H] = model.get("_viewport_size") || [800, 600];
|
|
4
|
+
|
|
5
|
+
const container = document.createElement("div");
|
|
6
|
+
container.style.position = "relative";
|
|
7
|
+
el.appendChild(container);
|
|
8
|
+
|
|
9
|
+
const canvas = document.createElement("canvas");
|
|
10
|
+
canvas.width = CANVAS_W;
|
|
11
|
+
canvas.height = CANVAS_H;
|
|
12
|
+
container.appendChild(canvas);
|
|
13
|
+
|
|
14
|
+
const ctx = canvas.getContext("2d");
|
|
15
|
+
|
|
16
|
+
const walls = model.get("wall_positions") || [];
|
|
17
|
+
let sim_state = model.get("sim_state") || {};
|
|
18
|
+
|
|
19
|
+
model.on("change:sim_state", () => {
|
|
20
|
+
sim_state = model.get("sim_state") || {};
|
|
21
|
+
});
|
|
22
|
+
const xs = [];
|
|
23
|
+
const ys = [];
|
|
24
|
+
|
|
25
|
+
for (const [x, y, w, h] of walls) {
|
|
26
|
+
const r = Math.hypot(w, h) * 0.5;
|
|
27
|
+
xs.push(x - r, x + r);
|
|
28
|
+
ys.push(y - r, y + r);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const min_x = Math.min(...xs);
|
|
32
|
+
const max_x = Math.max(...xs);
|
|
33
|
+
const min_y = Math.min(...ys);
|
|
34
|
+
const max_y = Math.max(...ys);
|
|
35
|
+
|
|
36
|
+
const scale = Math.min(
|
|
37
|
+
(CANVAS_W - 2) / (max_x - min_x),
|
|
38
|
+
(CANVAS_H - 2) / (max_y - min_y)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const offset_x = -min_x * scale;
|
|
42
|
+
const offset_y = max_y * scale;
|
|
43
|
+
|
|
44
|
+
const worldToScreen = (x, y) => {
|
|
45
|
+
return [
|
|
46
|
+
x * scale + offset_x,
|
|
47
|
+
-y * scale + offset_y,
|
|
48
|
+
];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const drawWalls = () => {
|
|
52
|
+
ctx.fillStyle = "#cccccc";
|
|
53
|
+
ctx.strokeStyle = "#000";
|
|
54
|
+
|
|
55
|
+
for (const [x, y, w, h, rot] of walls) {
|
|
56
|
+
const [sx, sy] = worldToScreen(x, y);
|
|
57
|
+
|
|
58
|
+
ctx.save();
|
|
59
|
+
ctx.translate(sx, sy);
|
|
60
|
+
const rad = (rot * Math.PI) / 180;
|
|
61
|
+
ctx.rotate(-rad);
|
|
62
|
+
|
|
63
|
+
ctx.beginPath();
|
|
64
|
+
ctx.rect(
|
|
65
|
+
(-w * scale) / 2,
|
|
66
|
+
(-h * scale) / 2,
|
|
67
|
+
w * scale,
|
|
68
|
+
h * scale
|
|
69
|
+
);
|
|
70
|
+
ctx.fill();
|
|
71
|
+
ctx.stroke();
|
|
72
|
+
|
|
73
|
+
ctx.restore();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const drawCars = () => {
|
|
78
|
+
if (!sim_state.x) return;
|
|
79
|
+
|
|
80
|
+
const xs = sim_state.x;
|
|
81
|
+
const ys = sim_state.y;
|
|
82
|
+
const angles = sim_state.angle;
|
|
83
|
+
const CAR_LENGTH = 1.0;
|
|
84
|
+
const CAR_WIDTH = 0.5;
|
|
85
|
+
const COLORS = [
|
|
86
|
+
"red",
|
|
87
|
+
"orange",
|
|
88
|
+
"yellow",
|
|
89
|
+
"green",
|
|
90
|
+
"blue",
|
|
91
|
+
"indigo",
|
|
92
|
+
"violet",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < xs.length; i++) {
|
|
96
|
+
const [sx, sy] = worldToScreen(xs[i], ys[i]);
|
|
97
|
+
|
|
98
|
+
ctx.save();
|
|
99
|
+
ctx.translate(sx, sy);
|
|
100
|
+
ctx.rotate(angles[i]);
|
|
101
|
+
|
|
102
|
+
ctx.fillStyle = COLORS[i % COLORS.length];
|
|
103
|
+
ctx.strokeStyle = "black";
|
|
104
|
+
|
|
105
|
+
ctx.beginPath();
|
|
106
|
+
ctx.rect(
|
|
107
|
+
(-CAR_LENGTH * scale) / 2,
|
|
108
|
+
(-CAR_WIDTH * scale) / 2,
|
|
109
|
+
CAR_LENGTH * scale,
|
|
110
|
+
CAR_WIDTH * scale
|
|
111
|
+
);
|
|
112
|
+
ctx.fill();
|
|
113
|
+
ctx.stroke();
|
|
114
|
+
|
|
115
|
+
ctx.restore();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const draw = () => {
|
|
120
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
121
|
+
|
|
122
|
+
ctx.fillStyle = "#ffffff";
|
|
123
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
124
|
+
|
|
125
|
+
drawWalls();
|
|
126
|
+
drawCars();
|
|
127
|
+
|
|
128
|
+
requestAnimationFrame(draw);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
draw();
|
|
132
|
+
|
|
133
|
+
model.set("_view_ready", true);
|
|
134
|
+
model.save_changes();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import math
|
|
3
|
+
from . import (
|
|
4
|
+
TopDownDrivingEnv,
|
|
5
|
+
CAR_LENGTH,
|
|
6
|
+
CAR_WIDTH,
|
|
7
|
+
LOCAL_WALLS,
|
|
8
|
+
CHECKPOINTS,
|
|
9
|
+
RAY_COUNT,
|
|
10
|
+
RAY_SPREAD,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import tkinter as tk
|
|
15
|
+
from .. import _tk_base
|
|
16
|
+
except ImportError:
|
|
17
|
+
raise ImportError("tkinter is required for MountainCarTkFrontend")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
CHECKPOINT_RADIUS = 0.85
|
|
21
|
+
COLOR_MAP = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
|
|
22
|
+
|
|
23
|
+
xs, ys = [], []
|
|
24
|
+
|
|
25
|
+
for x, y, w, h, rot in LOCAL_WALLS:
|
|
26
|
+
r = math.hypot(w, h) * 0.5
|
|
27
|
+
xs.extend([x - r, x + r])
|
|
28
|
+
ys.extend([y - r, y + r])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
min_x, max_x = min(xs), max(xs)
|
|
32
|
+
min_y, max_y = min(ys), max(ys)
|
|
33
|
+
|
|
34
|
+
CANVAS_W, CANVAS_H = 800, 600
|
|
35
|
+
scale = min((CANVAS_W - 2) / (max_x - min_x), (CANVAS_H - 2) / (max_y - min_y))
|
|
36
|
+
offset_x = -min_x * scale
|
|
37
|
+
offset_y = max_y * scale
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def rotated_rect(cx, cy, w, h, deg):
|
|
41
|
+
rad = math.radians(-deg)
|
|
42
|
+
c, s = math.cos(rad), math.sin(rad)
|
|
43
|
+
hw, hh = w / 2, h / 2
|
|
44
|
+
pts = []
|
|
45
|
+
for x, y in [(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)]:
|
|
46
|
+
rx = x * c - y * s
|
|
47
|
+
ry = x * s + y * c
|
|
48
|
+
pts.extend([cx + rx, cy + ry])
|
|
49
|
+
return pts
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def world_to_screen(x, y):
|
|
53
|
+
return x * scale + offset_x, -y * scale + offset_y
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TopDownDrivingTkFrontend(_tk_base.TkBaseFrontend):
|
|
57
|
+
|
|
58
|
+
def __init__(self, viewport_size=(800, 600), sim_env=None):
|
|
59
|
+
super().__init__()
|
|
60
|
+
if sim_env is None:
|
|
61
|
+
sim_env = TopDownDrivingEnv()
|
|
62
|
+
self.sim_env = sim_env
|
|
63
|
+
self._viewport_size = viewport_size
|
|
64
|
+
self.show_rays = False
|
|
65
|
+
|
|
66
|
+
self.keys = set()
|
|
67
|
+
|
|
68
|
+
async def step(self, action, dt=0.02):
|
|
69
|
+
state = self.sim_env.step(action)
|
|
70
|
+
|
|
71
|
+
if self._root:
|
|
72
|
+
self._root.after(0, lambda s=state: self._draw_state(self.sim_env))
|
|
73
|
+
|
|
74
|
+
await asyncio.sleep(dt)
|
|
75
|
+
return state
|
|
76
|
+
|
|
77
|
+
async def reset(self):
|
|
78
|
+
state = self.sim_env.reset()
|
|
79
|
+
if self._canvas:
|
|
80
|
+
self._draw_state(self.sim_env)
|
|
81
|
+
return state
|
|
82
|
+
|
|
83
|
+
def _create_window(self, root):
|
|
84
|
+
w, h = self._viewport_size
|
|
85
|
+
root.title("Top Down Driving")
|
|
86
|
+
canvas = tk.Canvas(root, width=w, height=h, bg="white")
|
|
87
|
+
canvas.pack(expand=True)
|
|
88
|
+
|
|
89
|
+
for x, y, w, h, rot in LOCAL_WALLS:
|
|
90
|
+
cx, cy = world_to_screen(x, y)
|
|
91
|
+
pts = rotated_rect(cx, cy, w * scale, h * scale, rot)
|
|
92
|
+
canvas.create_polygon(pts, fill="#cccccc", outline="black")
|
|
93
|
+
|
|
94
|
+
r = CHECKPOINT_RADIUS * scale
|
|
95
|
+
for i, (x, y) in enumerate(CHECKPOINTS):
|
|
96
|
+
sx, sy = world_to_screen(x, y)
|
|
97
|
+
|
|
98
|
+
canvas.create_oval(
|
|
99
|
+
sx - r,
|
|
100
|
+
sy - r,
|
|
101
|
+
sx + r,
|
|
102
|
+
sy + r,
|
|
103
|
+
fill="green",
|
|
104
|
+
outline="",
|
|
105
|
+
stipple="gray25",
|
|
106
|
+
)
|
|
107
|
+
# add checkpoint number
|
|
108
|
+
canvas.create_text(
|
|
109
|
+
sx, sy, text=str(i + 1), fill="white", font=("Arial", int(r))
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
root.bind("<KeyPress>", lambda e: self.keys.add(e.keysym))
|
|
113
|
+
root.bind("<KeyRelease>", lambda e: self.keys.discard(e.keysym))
|
|
114
|
+
|
|
115
|
+
self.bring_to_front(root)
|
|
116
|
+
self._root = root
|
|
117
|
+
self._canvas = canvas
|
|
118
|
+
self._draw_state(self.sim_env)
|
|
119
|
+
self._pump()
|
|
120
|
+
root.mainloop()
|
|
121
|
+
|
|
122
|
+
def _draw_state(self, sim_env):
|
|
123
|
+
if not self._canvas:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
c = self._canvas
|
|
127
|
+
c.delete("car")
|
|
128
|
+
c.delete("ray")
|
|
129
|
+
|
|
130
|
+
xs = sim_env.x
|
|
131
|
+
ys = sim_env.y
|
|
132
|
+
angles = sim_env.angle
|
|
133
|
+
n = len(xs)
|
|
134
|
+
|
|
135
|
+
for i in range(n):
|
|
136
|
+
cx, cy = world_to_screen(xs[i], ys[i])
|
|
137
|
+
|
|
138
|
+
pts = rotated_rect(
|
|
139
|
+
cx,
|
|
140
|
+
cy,
|
|
141
|
+
CAR_LENGTH * scale,
|
|
142
|
+
CAR_WIDTH * scale,
|
|
143
|
+
math.degrees(angles[i]),
|
|
144
|
+
)
|
|
145
|
+
c.create_polygon(
|
|
146
|
+
pts,
|
|
147
|
+
fill=COLOR_MAP[i % len(COLOR_MAP)],
|
|
148
|
+
outline="black",
|
|
149
|
+
tags="car",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not self.show_rays:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
ray_offsets = [
|
|
156
|
+
-RAY_SPREAD * 0.5 + RAY_SPREAD * i / (RAY_COUNT - 1)
|
|
157
|
+
for i in range(RAY_COUNT)
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
for i in range(n):
|
|
161
|
+
ox, oy = xs[i], ys[i]
|
|
162
|
+
base = angles[i]
|
|
163
|
+
|
|
164
|
+
sx1, sy1 = world_to_screen(ox, oy)
|
|
165
|
+
|
|
166
|
+
for r_idx, dist in enumerate(sim_env.rays[i]):
|
|
167
|
+
a = base + ray_offsets[r_idx]
|
|
168
|
+
x2 = ox + math.cos(a) * dist
|
|
169
|
+
y2 = oy + math.sin(a) * dist
|
|
170
|
+
sx2, sy2 = world_to_screen(x2, y2)
|
|
171
|
+
|
|
172
|
+
c.create_line(
|
|
173
|
+
sx1,
|
|
174
|
+
sy1,
|
|
175
|
+
sx2,
|
|
176
|
+
sy2,
|
|
177
|
+
fill="red",
|
|
178
|
+
width=1,
|
|
179
|
+
tags="ray",
|
|
180
|
+
)
|