This repository has been archived by the owner on May 3, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simdemo.js
340 lines (277 loc) · 9.06 KB
/
simdemo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
dojo.require("dojox.gfx");
// constants adjust speed the robot rotates/moves by multiplying.
rotationConst = 1 / 180;
translationConst = 0.3;
updateRate = 15; // how many frames a second are rendered
updateInt = 1000/updateRate; // update interval in milliseconds
anim_interval = null; // holds ID of setInterval timeout for animation
code_timeout = null; // holds ID of setTimeout timeout for code pauses
// yield str and regex are used for locating yields to replace with tuples
yield_str = "yield"; // string used by code editor
yield_regex = /^(\s*)yield\s+([^#\n]+)\s*(?:#.*)*$/gm;
// Run once on page load
function init ()
{
// ensure rate counter on page starts at correct value
document.nextform.rateDisplay.value=updateRate;
var container = dojo.byId("simDisplay");
surface = dojox.gfx.createSurface(container, 500, 500);
setupAnim();
editor = CodeMirror.fromTextArea('code', {
parserfile: ["../contrib/python/js/parsepython.js"],
stylesheet: ["CodeMirror-0.63/contrib/python/css/pythoncolors.css"],
path: "CodeMirror-0.63/js/",
lineNumbers: true,
textWrapping: false,
indentUnit: 4,
width: "350px",
height: "470px",
parserConfig: {'pythonVersion': 2, 'strictErrors': true}
});
}
// Triggered by buttons on page.
function changeUpdateRate(val)
{
updateRate = updateRate + val;
document.nextform.rateDisplay.value=updateRate;
updateInt = 1000/updateRate;
// change update rate in currently running animation
updateAnim();
}
// Initiated by pressing "Run" button
function loadCode()
{
// get text from editor
var code_orig=editor.getCode();
// find yields in editor
var yields = findYields();
// replace yields in text
var code_mod = replaceYields(code_orig,yields);
// compile with Skulpt
var compiled_code = compileCode(code_mod);
// stopAnim just in case
stopAnim();
// start automatic stepping
startAnim();
// finally run the code
var success = runCode(compiled_code);
if (!success) // stop animation if code failed to run
{
stopAnim();
}
}
// Generate an ordered array of line numbers for lines
// with a valid yield statement.
function findYields()
{
var yields = [];
var searchObj = editor.getSearchCursor(yield_str); // assumes 4-space indentation
var found = searchObj.findNext(); // true if it finds such a thing
while (found == true) // loop through all instances of yield
{
var content = editor.lineContent(searchObj.line);
// now need to check if it actually matches the regex:
if (content.search(yield_regex) != -1)
{
var lineNum = editor.lineNumber(searchObj.line);
yields.push(lineNum);
}
found = searchObj.findNext(); // get next result
}
return yields;
}
// Goes through the code modifying valid yield statements
// to pass a tuple, in which the first value is the line number
// that the yield appeared in (stored in our yield array) and the
// second value is the original value the yield passed.
function replaceYields(code, yields)
{
newcode = code.replace(yield_regex,
function(wholematch, $1, $2)
{
var lineNum = yields.shift();
str = $1 + "yield (" + lineNum + ", " + $2 + ")";
return str;
});
return newcode;
}
// Compile input code with skulpt.
function compileCode(input)
{
try
{
console.log("Code to be compiled:\n", input);
// Compile python into javascript
var js = Skulpt.compileStr("face", input);
if (js === false)
{
console.log("Compilation quietly failed.");
}
else
{
console.log("Compiled code: ", js);
}
}
catch (e)
{
console.log("Error compiling code: ", e );
}
return js;
}
// Try to run compiled robot code.
// Then try to creat instance of main() generator
// which should be defined in robot code.
// Lastly call loopCode which will keep calling itself with a new value to wait for.
function runCode(robotCode)
{
try
{
eval.call(window, robotCode);
}
catch (e)
{
console.log("Error running compiled code: ", e);
return false; // failed
}
try
{
robotCodeGen = main();
}
catch (e)
{
console.log("Error creating main() generator instance: ", e);
return false; // failed
}
loopCode();
return true; // succeeded
}
// Step once through user code generator.
// Use the yielded value to:
// * highlight relevent line in user code
// * setup loopCode to be called again after specified time.
function loopCode()
{
try
{
var yielded = robotCodeGen.next();
}
catch(e)
{
// this should catch a StopIteration style error when
// the generator has completed but currently skulpt doesn't do that.
console.log("Error running code?, ", e);
return;
}
if (yielded != undefined)
{
var waitfor = yielded.v[1] * 1000;
var lineNum = parseInt(yielded.v[0]);
selectLine(lineNum);
//console.log("yielding for: ", waitfor/1000, " seconds, line ", lineNum);
code_timeout = setTimeout(loopCode, waitfor);
}
else
{
console.log("finished running code");
stopAnim(); // stop animation
}
}
// Find line in editor, check length and select.
function selectLine(lineNum)
{
var line = editor.nthLine(lineNum);
var lineLen = editor.lineContent(line).length;
editor.selectLines(line, 0, line, lineLen);
}
// Setup new animation
function setupAnim()
{
stopAnim(); // stop any running animation
// initial values on animation creation/reset.
motorspeed = {a:0, b:0};
robot_pos = {x: 250, y: 250}; // absolute
robot_rot = 0; // absolute, in radians
// clear gfx surface
surface.clear();
var robot_points = [{x: -20, y: -20}, {x: -20, y: 20}, {x: 0, y: 10}, {x: 20, y: 20}, {x: 20, y: -20}, {x: -20, y: -20}];
robot = surface.createPolyline(robot_points)
.setStroke({color: "black", width: 2})
.setFill("#889");
robot.setTransform( makeTransformMatrix(robot_pos.x, robot_pos.y, robot_rot) );
}
// Change automatic updating based on new updateInt
function updateAnim ()
{
if (anim_interval != null)
{
clearInterval(anim_interval); // stop updating display
anim_interval = setInterval(step, updateInt);
}
}
// Start automatic updating animation
function startAnim ()
{
anim_interval = setInterval(step, updateInt);
}
// Stop automatic updating animation
function stopAnim ()
{
clearInterval(anim_interval); // stop updating display
clearInterval(code_timeout); // cancel any waiting code timeout
}
// Part of robot programming API.
// Sets each motor speed individually.
function setspeed (motora, motorb)
{
motorspeed = {a:motora, b:motorb};
}
// From two motor speeds estimate a rotation component.
function getNewRotation (motorLeft, motorRight)
{
var difference = (motorLeft - motorRight) * updateInt/1000 * rotationConst;
var newRot = robot_rot + difference;
newRot %= Math.PI * 2; // keep value between 0 and 2*PI
return newRot;
}
// From two motorspeeds and absolute rotation work out
// forward distance and from this translation coordinates.
function getNewPosition (motorLeft, motorRight, rotation)
{
var distance = (motorLeft + motorRight) * updateInt/1000 * translationConst / 2;
var deltaX = -distance * Math.sin(robot_rot);
var deltaY = distance * Math.cos(robot_rot);
var posX = robot_pos.x + deltaX;
var posY = robot_pos.y + deltaY;
return {x: posX, y: posY};
}
// Create a 2D transformation matrix for the robot.
function makeTransformMatrix (posX, posY, rotation)
{
// Create a rotation matrix about the origin (robot is drawn with it's centre at the origin).
var matrix_rot = dojox.gfx.matrix.rotate(rotation);
// Create a translation matrix from the origin to the new position.
var matrix_pos = dojox.gfx.matrix.translate(posX, posY);
// Finally multiply the two matrices together.
// The order of multiplication is important.
var matrix_final = dojox.gfx.matrix.multiply(matrix_pos, matrix_rot);
return matrix_final;
}
// Update animation by one frame
function step()
{
var motorLeft = motorspeed.a;
var motorRight = motorspeed.b;
// Find and set new position and rotation
var new_robot_rot = getNewRotation(motorLeft,motorRight);
var new_robot_pos = getNewPosition(motorLeft, motorRight, robot_rot);
// Get a new transformation matrix for the new absolute position/rotation details.
var matrix_final = makeTransformMatrix(new_robot_pos.x, new_robot_pos.y, new_robot_rot);
// Update position/rotation values:
robot_rot = new_robot_rot;
robot_pos = new_robot_pos;
// Replace the robot transform with the new transformation matrix.
// This is a different operation to gfx.shape.applyTransform,
// which multiplies a new transform with the existing one.
robot.setTransform(matrix_final);
}
dojo.addOnLoad(init);