diff --git a/package.json b/package.json index 2d4d54773..2b6d7335a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dependencies": { "htmlparser2": "3.9.0", "memoizee": "0.3.10", - "promise": "7.1.1" + "promise": "7.1.1", + "scratch-render-webgl": "git+https://github.com/LLK/scratch-render-webgl.git" }, "devDependencies": { "eslint": "2.7.0", diff --git a/playground/index.html b/playground/index.html index 82535807b..e11d72c82 100644 --- a/playground/index.html +++ b/playground/index.html @@ -13,9 +13,14 @@ <button id="greenflag">Green flag</button> <button id="stopall">Stop</button> <p> + <a id="renderexplorer-link" href="#">Renderer</a><br /> <a id="threadexplorer-link" href="#">VM Threads</a><br /> <a id="blockexplorer-link" href="#">VM Block Representation</a> </p> + <div id="tab-renderexplorer"> + Render<br /> + <canvas id="scratch-stage" style="width: 480px; height: 360px;"></canvas> + </div> <div id="tab-threadexplorer"> Thread explorer <pre id="threadexplorer"></pre> @@ -240,6 +245,54 @@ </value> </block> </category> + <category name="Motion"> + <block type="motion_movesteps"> + <value name="STEPS"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_turnright"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_turnleft"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_pointindirection"> + <value name="DIRECTION"> + <shadow type="math_number"> + <field name="NUM">90</field> + </shadow> + </value> + </block> + <block type="motion_pointtowards"> + <value name="TOWARDS"> + <shadow type="motion_pointtowards_menu"> + </shadow> + </value> + </block> + <block type="motion_gotoxy"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + </category> </xml> <!-- Syntax highlighter --> @@ -249,6 +302,8 @@ <script src="../node_modules/scratch-blocks/blockly_compressed_vertical.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed_vertical.js"></script> + <!-- Renderer --> + <script src="../node_modules/scratch-render-webgl/build/render-webgl.js"></script> <!-- VM Worker --> <script src="../vm.worker.js"></script> <!-- Playground --> diff --git a/playground/playground.css b/playground/playground.css index 451a7cad5..a15e1d81c 100644 --- a/playground/playground.css +++ b/playground/playground.css @@ -32,6 +32,6 @@ a { font-size: 10pt; } -#tab-blockexplorer { +#tab-blockexplorer, #tab-threadexplorer { display: none; } diff --git a/playground/playground.js b/playground/playground.js index be683ceb7..220549c9f 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -3,6 +3,10 @@ window.onload = function() { var vm = new window.VirtualMachine(); window.vm = vm; + var canvas = document.getElementById('scratch-stage'); + window.renderer = new window.RenderWebGLLocal(canvas); + window.renderer.connectWorker(window.vm.vmWorker); + var toolbox = document.getElementById('toolbox'); var workspace = window.Blockly.inject('blocks', { toolbox: toolbox, @@ -85,16 +89,25 @@ window.onload = function() { var tabBlockExplorer = document.getElementById('tab-blockexplorer'); var tabThreadExplorer = document.getElementById('tab-threadexplorer'); + var tabRenderExplorer = document.getElementById('tab-renderexplorer'); // Handlers to show different explorers. document.getElementById('threadexplorer-link').addEventListener('click', function () { tabBlockExplorer.style.display = 'none'; + tabRenderExplorer.style.display = 'none'; tabThreadExplorer.style.display = 'block'; }); document.getElementById('blockexplorer-link').addEventListener('click', function () { tabBlockExplorer.style.display = 'block'; + tabRenderExplorer.style.display = 'none'; + tabThreadExplorer.style.display = 'none'; + }); + document.getElementById('renderexplorer-link').addEventListener('click', + function () { + tabBlockExplorer.style.display = 'none'; + tabRenderExplorer.style.display = 'block'; tabThreadExplorer.style.display = 'none'; }); }; diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js new file mode 100644 index 000000000..4fcc3b056 --- /dev/null +++ b/src/blocks/scratch3_motion.js @@ -0,0 +1,31 @@ +function Scratch3MotionBlocks(runtime) { + /** + * The runtime instantiating this block package. + * @type {Runtime} + */ + this.runtime = runtime; +} + +/** + * Retrieve the block primitives implemented by this package. + * @return {Object.<string, Function>} Mapping of opcode to Function. + */ +Scratch3MotionBlocks.prototype.getPrimitives = function() { + return { + 'motion_gotoxy': this.goToXY, + 'motion_turnright': this.turnRight + }; +}; + +Scratch3MotionBlocks.prototype.goToXY = function (args, util) { + util.target.setXY(args.X, args.Y); +}; + +Scratch3MotionBlocks.prototype.turnRight = function (args, util) { + if (args.DEGREES !== args.DEGREES) { + throw "Bad degrees" + args.DEGREES; + } + util.target.setDirection(args.DEGREES + util.target.direction); +}; + +module.exports = Scratch3MotionBlocks; diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index 75077061c..86cb50350 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -31,7 +31,12 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() { }; Scratch3OperatorsBlocks.prototype.number = function (args) { - return Number(args.NUM); + var num = Number(args.NUM); + if (num !== num) { + // NaN + return 0; + } + return num; }; Scratch3OperatorsBlocks.prototype.text = function (args) { diff --git a/src/engine/execute.js b/src/engine/execute.js index 001178d56..aa413a76b 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -70,7 +70,8 @@ var execute = function (sequencer, thread) { stackFrame: currentStackFrame.executionContext, startSubstack: function (substackNum) { sequencer.stepToSubstack(thread, substackNum); - } + }, + target: target }); // Deal with any reported value. diff --git a/src/engine/runtime.js b/src/engine/runtime.js index a0db919ae..c51800cdb 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -6,6 +6,7 @@ var util = require('util'); var defaultBlockPackages = { 'scratch3_control': require('../blocks/scratch3_control'), 'scratch3_event': require('../blocks/scratch3_event'), + 'scratch3_motion': require('../blocks/scratch3_motion'), 'scratch3_operators': require('../blocks/scratch3_operators') }; @@ -257,6 +258,9 @@ Runtime.prototype._setInterval = function(fcn, interval) { Runtime.prototype.start = function () { this._setInterval(function() { this._step(); + if (self.renderer) { + self.renderer.draw(); + } }.bind(this), Runtime.THREAD_STEP_INTERVAL); }; diff --git a/src/index.js b/src/index.js index 08109c0a9..fff9c52db 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,7 @@ function VirtualMachine () { // @todo support multiple targets/sprites. // This is just a demo/example. var exampleSprite = new Sprite(); + exampleSprite.createClone(); var exampleTargets = [exampleSprite.clones[0]]; instance.exampleSprite = exampleSprite; instance.runtime = new Runtime(exampleTargets); @@ -96,6 +97,10 @@ VirtualMachine.prototype.getPlaygroundData = function () { * from a worker environment. */ if (ENV_WORKER) { + self.importScripts( + './node_modules/scratch-render-webgl/build/render-webgl-worker.js' + ); + self.renderer = new self.RenderWebGLWorker(); self.vmInstance = new VirtualMachine(); self.onmessage = function (e) { var messageData = e.data; @@ -123,7 +128,11 @@ if (ENV_WORKER) { }); break; default: - throw 'Unknown method' + messageData.method; + if (e.data.id == 'RendererConnected') { + //initRenderWorker(); + } + self.renderer.onmessage(e); + break; } }; // Bind runtime's emitted events to postmessages. diff --git a/src/sprites/clone.js b/src/sprites/clone.js index afbd56f1c..f4f99abbe 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -3,9 +3,19 @@ var Target = require('../engine/target'); function Clone(spriteBlocks) { Target.call(this, spriteBlocks); + this.drawableID = null; + this.initDrawable(); } util.inherits(Clone, Target); +Clone.prototype.initDrawable = function () { + var createPromise = self.renderer.createDrawable(); + var instance = this; + createPromise.then(function (id) { + instance.drawableID = id; + }); +}; + // Clone-level properties Clone.prototype.x = 0; @@ -13,4 +23,19 @@ Clone.prototype.y = 0; Clone.prototype.direction = 90; +Clone.prototype.setXY = function (x, y) { + this.x = x; + this.y = y; + self.renderer.updateDrawableProperties(this.drawableID, { + position: [this.x, this.y] + }); +}; + +Clone.prototype.setDirection = function (direction) { + this.direction = direction; + self.renderer.updateDrawableProperties(this.drawableID, { + direction: this.direction + }); +}; + module.exports = Clone; diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index 1608b0571..1ec25b3ba 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -9,9 +9,12 @@ function Sprite (blocks) { } this.blocks = blocks; this.clones = []; - - // Initial single clone with the shared blocks. - this.clones.push(new Clone(this.blocks)); } +Sprite.prototype.createClone = function () { + var newClone = new Clone(this.blocks); + this.clones.push(newClone); + return newClone; +}; + module.exports = Sprite;