FelixMcFelix/laughing-ironman

View on GitHub
src/MVM/MVM.js

Summary

Maintainability
F
2 wks
Test Coverage
/*
* Sketch Virtual Machine
* Darren Findlay
*
* 15th January 2015
*
*/

var MVM = MVM || {};

/**
 * @classdesc The virtual machine used to execute Sketch programs.
 * @class MVM.VM
 * @param {WebGLRenderingContext} glctx - WebGL context of the canvas to operate on.
 * @param {Palette.Manager} manager - Shader Manager used to abstract draw calls and shader manipulation.
 * @param {Array} codeStore - MVM program code generated by an instance of {@link Sketch.SketchGen}.
 * @param {boolean} debugMode - print additional information to the console during execution.
 * @public
 * @author Darren Findlay
 */
MVM.VM = function(glctx, manager, codeStore, debugMode) {

    /*
    *    Struct layouts
    *    
    *                  x    y
    *    Point         [100, 150]
    *
    *                  r    g   b   a   x1  y1  x2  y2
    *    Line         [ 0 ,  0 ,255,255,100,100,200,200]
    *
    *                  r    g   b   a   x1  y1  x2  y2  x3  y3     0 or More points
    *    Polygon     [ 0 ,  0 ,255,255,100,100,200,200,150, 0 ,.............]
    *
    */

    //TEMP
    var constantPool = [];
    var labelTable = [];

    // WebGL context
    var glctx = glctx;

    // Shader manager
    var manager = manager;

    // Loop Counter - For debugging
    var lc = 0;

    // Points to the next instruction in the code store to execute
    var cp = 0;

    // Points to the first free location after the program
    var cl;

    // Data store (Stack)
    window.MVM.dataStore = [];
    var data = new MVM.DataModel();

    // Points to the first free space at the top of the data store
    var sp = 0;

    // Points to the first location of the top most frame
    var fp = 0;

    // Local Offset. The off set of the first local address from the frame pointer
    var LO = 2;

    // Address of the dynamic link in a frame
    var DLA = 0;

    // Address of the retrun address of a frame
    var RA = 1;

    // Global data store
    var globalStore = [];

    // Flags wether the virtual machine should hand over control
    // to the browser so te canvas can be rendered
    var needsUpdate = 0;

    this.dead = false;

    this.interpret = function() {
        if(this.dead === true){
            return;
        }

        var dataStore = window.MVM.dataStore;

        if(debugMode) console.log(codeStore);

        cl = codeStore.length;

        var opCodes = MVM.opCodes;
        while (cp < cl && needsUpdate == 0) {
            lc++
            var opCode = codeStore[cp];
            cp++;
            switch (opCode) {
                case opCodes.STOREG:
                    //Store a value in a given relative stack frame, in a given index.
                    //USE: STOREG index
                    //e.g. STORER 0 stores the top value on the stack in slot 0 of the root scope frame.
                    var ind = codeStore[cp++];
                    var val = data.current()
                                  .pop();

                    data.root
                        .setVar(ind, val);

                    if(debugMode) console.log("STOREG: " + val + " in index " + ind);
                    break;
                case opCodes.LOADG:
                    //Load a value from the root scope frame onto the current stack, from a given index.
                    //USE: LOADG index
                    //e.g. LOADG 0 loads the value in slot 0 of the root scope frame.
                    var ind = codeStore[cp++];    
                    var val = data.root
                                  .getVar(ind);

                    data.current()
                        .push(val);

                    if(debugMode) console.log("LOADG: " + val + " from index " + ind);
                    break;
                case opCodes.STOREL:
                    //Store a value in the current scope frame, in a given index.
                    //USE: STOREL index
                    //e.g. STOREL 0 stores the top value on the stack in slot 0 of the current scope frame.
                    var ind = codeStore[cp++];
                    var val = data.current()
                                  .pop();

                    data.current()
                        .setVar(ind, val);

                    if(debugMode) console.log("STOREL: " + val + " in index " + ind);
                    break;
                case opCodes.LOADL:
                    //Load a value from the current scope frame onto the current stack, from a given index.
                    //USE: LOADL index
                    //e.g. LOADL 0 loads the value in slot 0 of the data stack frame.
                    var ind = codeStore[cp++];    
                    var val = data.current()
                                  .getVar(ind);

                    data.current()
                        .push(val);

                    if(debugMode) console.log("LOADL: "  + val + " from index " + ind);
                    break;
                case opCodes.LOADC:
                    //Place the next codeword on the top of the stack.
                    var constant = codeStore[cp++];

                    data.current()
                        .push(constant);

                    if(debugMode) console.log("LOADC: " + constant);
                    break;
                case opCodes.IADD:
                    //Pop two integers off the stack, add them and push the new result onto the stack.
                    var i = Math.floor(
                        data.current()
                            .pop());
                    var j = Math.floor(
                        data.current()
                            .pop());
                    var result = j + i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("IADD: " + j + " + " + i + " = " + result);
                    break;
                case opCodes.ISUB:
                    //Pop two integers off the stack, subbtract them and push the new result onto the stack.
                    var i = Math.floor(
                        data.current()
                            .pop());
                    var j = Math.floor(
                        data.current()
                            .pop());
                    var result = j - i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("ISUB: " + j + " - " + i + " = " + result);
                    break;
                case opCodes.IMUL:
                    //Pop two integers off the stack, multiply them and push the new result onto the stack.
                    var i = Math.floor(
                        data.current()
                            .pop());
                    var j = Math.floor(
                        data.current()
                            .pop());
                    var result = j * i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("IMUL: " + j + " * " + i + " = " + result);
                    break;
                case opCodes.IDIV:
                    //Pop two integers off the stack, divide them and push the new result onto the stack.
                    var i = Math.floor(
                        data.current()
                            .pop());
                    var j = Math.floor(
                        data.current()
                            .pop());

                    var result = Math.floor(j / i);
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("IDIV: " + j + " / " + i + " = " + result);
                    break;
                case opCodes.IMOD:
                    //Pop two integers off the stack, take modulus and push the new result onto the stack.
                    var i = Math.floor(
                        data.current()
                            .pop());
                    var j = Math.floor(
                        data.current()
                            .pop());
                    var result = Math.floor(j % i);
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("IMOD: " + j + " % " + i + " = " + result);
                    break;
                case opCodes.FADD:
                    //Pop two integers off the stack, add them and push the new result onto the stack.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();
                    var result = j + i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("FADD: " + j + " + " + i + " = " + result);
                    break;
                case opCodes.FSUB:
                    //Pop two integers off the stack, subtract them and push the new result onto the stack.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();
                    var result = j - i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("FSUB: " + j + " - " + i + " = " + result);
                    break;
                case opCodes.FMUL:
                    //Pop two integers off the stack, multiply them and push the new result onto the stack.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();
                    var result = j * i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("FMUL: " + j + " * " + i + " = " + result);
                    break;
                case opCodes.FDIV:
                    //Pop two integers off the stack, divide them and push the new result onto the stack.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();
                    var result = j / i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("FDIV: " + j + " / " + i + " = " + result);
                    break;
                case opCodes.FMOD:
                    //Pop two integers off the stack, divide them and push the remainder onto the stack.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();
                    var result = j % i;

                    data.current()
                        .push(result);

                    if(debugMode) console.log("FMOD: " + j + " % " + i + " = " + result);
                    break;
                case opCodes.CMPEQ:
                    //Pop two values off the stack, push true if they equate or false if they do not.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();

                    var result = (j == i);
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("CMPEQ: " + j + " == " + i + " = " + result);
                    break;
                case opCodes.CMPLT:
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();

                    var result = (j < i);
                    
                    data.current()
                        .push(result);
                    if(debugMode) console.log("CMPLT: " + j + " < " + i + " = " + result);
                    break;
                case opCodes.CMPGT:
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();

                    var result = (j > i);
                    
                    data.current()
                        .push(result);
                    if(debugMode) console.log("CMPGT: " + j + " > " + i + " = " + result);
                    break;
                case opCodes.JUMP:
                    //Jump to another part of the program unconditionally.
                    //USE: JUMP address

                    var address = codeStore[cp];
                    cp = address;

                    if(debugMode) console.log("JUMP: " + address);
                    break;
                case opCodes.JUMPT:
                    //Jump to another part of the program if the top value on the stack is true.
                    //USE: JUMPT address

                    var address = codeStore[cp++];
                    
                    var i = data.current()
                                .pop();
                    if (i) {
                        cp = address;
                    }

                    if(debugMode) console.log("JUMPT: to " + address + ", cond is " + i);
                    break;
                case opCodes.JUMPF:
                    //Jump to another part of the program if the top value on the stack is false.
                    //USE: JUMPF address

                    var address = codeStore[cp++];
                    
                    var i = data.current()
                                .pop();
                    if (!i) {
                        cp = address;
                    }
                    
                    if(debugMode) console.log("JUMPF: to " + address + ", cond is " + i);
                    break;
                case opCodes.CALL:
                    //Call a function.
                    //USE: CALL definitionHeight codeAddress numArgs
                    //e.g. CALL 1 90 3 calls a function starting at code address 90, defined 1 scope frame above the call site with 3 parameters.

                    var definitionHeight = codeStore[cp++];
                    var codeAddress = codeStore[cp++];
                    var numArgs = codeStore[cp++];

                    var returnAddress = cp;

                    data.call(numArgs, definitionHeight, returnAddress);
                    cp = codeAddress;

                    if(debugMode) console.log("CALL: " + codeAddress + "  with " + numArgs + " arguments, return to " + returnAddress);
                    break;
                case opCodes.RETURNVAL:
                    //Return from a function, taking the top value from the stack from the function scope and placing it onto the resumed scope.
                    //USE: RETURNVAL
                    var value = data.current()
                                    .pop();

                    cp = data.funcreturn(value);

                    if(debugMode) console.log("RETURNVAL: " + value + " returned, exiting function. New code pointer is "+cp);
                    break;
                case opCodes.RETURN:
                    //Return from a function, returning no value.
                    //USE: RETURN
                    cp = data.funcreturn(null);

                    if(debugMode) console.log("RETURN: void return, exiting function.");
                    break;
                case opCodes.LNDRAW:
                    // Get line from top of stack, and then draw it? What more is there to say?
                    var lineStruct = data.current()
                                           .pop();

                    var r = lineStruct[0];
                    var g = lineStruct[1];
                    var b = lineStruct[2];
                    var a = lineStruct[3];
                    var pt1x = lineStruct[4];
                    var pt1y = lineStruct[5];
                    var pt2x = lineStruct[6];
                    var pt2y = lineStruct[7];

                    var theLine = new Float32Array([pt1x,pt1y,0, pt2x,pt2y,0]);
                    var theColor = new Float32Array([r,g,b,a]);

                    var prog = manager.getProgram("square", "square");
                    var canWidth = glctx.canvas.width;
                    var canHeight = glctx.canvas.height;
                    prog.setDrawMode(Palette.Program.LINES);
                    prog.draw(theLine, {width:[canWidth], height: [canHeight]}, {color: theColor})

                    if(debugMode) console.log("LNDRAW: " + lineStruct);
                    break;
                case opCodes.PGDRAW:
                    // Get polygon from top of stack, and then draw it? What more is there to say?
                    var polygonStruct = data.current()
                                              .pop();

                    var r = polygonStruct[0];
                    var g = polygonStruct[1];
                    var b = polygonStruct[2];
                    var a = polygonStruct[3];
                    var theColor = new Float32Array([r,g,b,a]);

                    var points = polygonStruct.slice(4);
                    // var i;
                    // for (i = 4; i < polygonStruct.length; i+=2) {
                    //     var pt = [polygonStruct[i],polygonStruct[i+1]];
                    //     points.push(pt);
                    // }
                    var prog = manager.getProgram("square", "square");
                    prog.setDrawMode(Palette.Program.POLYGON);
                    var canWidth = glctx.canvas.width;
                    var canHeight = glctx.canvas.height;

                    prog.draw(points, {width:[canWidth], height: [canHeight]}, {color: theColor})
                    if(debugMode) console.log("PGDRAW: " + polygonStruct);
                    break;
                case opCodes.RENDER:
                    needsUpdate = 1;
                    if(debugMode) console.log("RENDER");
                    break;
                case opCodes.CLEAR:
                    //Pop an element off the stack. If it is a colour, set it as the clear colour - if not, use the current clear colour.
                    var colour = data.current()
                                     .pop();
                    if(colour){
                        if(colour.length<4){
                            colour[3] = 1.0;
                        }

                        glctx.clearColor(colour[0],colour[1],colour[2],colour[3]);
                    }
                    
                    glctx.clear(glctx.COLOR_BUFFER_BIT|glctx.DEPTH_BUFFER_BIT);
                    if(debugMode) console.log("CLEAR");
                    break;
                case opCodes.EXIT:
                    //Ends program operation.
                    //USE: EXIT
                    cp = cl;
                    if(debugMode) console.log("EXIT");
                    break;
                case opCodes.LOADIDX:
                    var constPoolindex = codeStore[cp];
                    cp++;
                    var arrayIndex = codeStore[cp];
                    cp++;
                    var arr = constantPool[constPoolindex];
                    var value = arr[arrayIndex];
                    dataStore[sp] = value;
                    sp++;
                    if(debugMode) console.log("LOADIDX: constant pool index " + constPoolindex + " array index: " + arrayIndex);
                    break;
                case opCodes.SETIDX:
                    var constPoolindex = codeStore[cp];
                    cp++;
                    var arrayIndex = codeStore[cp];
                    cp++;
                    var arr = constantPool[constPoolindex];
                    sp--;
                    var value = dataStore[sp];
                    arr[arrayIndex] = value;
                    if(debugMode) console.log("SETIDX: constant pool index " + constPoolindex + " array index: " + arrayIndex);
                    break;
                case opCodes.LNTOPG:
                    sp--;
                    var lineAddress = dataStore[sp];
                    sp--;
                    var numSides = dataStore[sp];
                    var line = constantPool[lineAddress];
                    var polygon = line.slice(0);
                    var i = 2;
                    var angle = 360 / numSides;
                    var pt1xIdx = 4;
                    var pt1yIdx = 5;
                    var pt2xIdx = 6;
                    var pt2yIdx = 7;
                    var pt3xIdx = 8;
                    var pt3yIdx = 9;
                    while(i < numSides) {
                        var pivot = [polygon[pt1xIdx],polygon[pt1yIdx]];
                        var point = [polygon[pt2xIdx],polygon[pt2yIdx]];
                        var newpt = rotatePoint(pivot,point,angle);
                        // Shift new point
                        offSetx = polygon[pt1xIdx] - polygon[pt2xIdx];
                        offSety = polygon[pt1yIdx] - polygon[pt2yIdx];
                        newpt[0] -= offSetx;
                        newpt[1] -= offSety;
                        // Add new point to polygon
                        polygon[pt3xIdx] = newpt[0];
                        polygon[pt3yIdx] = newpt[1];
                        pt1xIdx += 2;
                        pt1yIdx += 2;
                        pt2xIdx += 2;
                        pt2yIdx += 2;
                        pt3xIdx += 2;
                        pt3yIdx += 2;
                        i++;
                    }
                    var targetAddress = codeStore[cp];
                    cp++;
                    constantPool[targetAddress] = polygon;
                    if(debugMode) console.log("LNTOPG " + polygon);
                    break;
                case opCodes.PTADD:
                    //Pop two points off the stack, then place a white line generated by this back onto the stack.
                    var pt2 = data.current()
                                  .pop();
                    var pt1 = data.current()
                                  .pop();
                    
                    var line = [1,1,1,1,pt1[0],pt1[1],pt2[0],pt2[1]];
                    
                    data.current()
                        .push(line);

                    if(debugMode) console.log("PTADD " + line);
                    break;
                case opCodes.LNMUL:
                    sp--;
                    var mulValue = dataStore[sp];
                    sp--;
                    var lineAddress = dataStore[sp];
                    var line = constantPool[lineAddress];
                    pt1x = line[4];
                    pt1y = line[5];
                    pt2x = line[6];
                    pt2y = line[7];

                    var xDist = pt2x - pt1x;
                    var yDist = pt2y - pt1y;

                    var xLen = (xDist * mulValue) - xDist; 
                    var yLen = (yDist * mulValue) - yDist; 

                    var targetLineAddress = codeStore[cp];
                    cp++;

                    var newLine = line.slice(0);
                    newLine[6] += xLen;
                    newLine[7] += yLen;
                    newLine[0] = 0;
                    newLine[1] = 1;
                    newLine[2] = 0;
                    newLine[3] = 1;
                    constantPool[targetLineAddress] = newLine;
                    if(debugMode) console.log("LNMUL " + newLine);
                    break;
                //Augmentations to support scoping.
                case opCodes.STORER:
                    //Store a value in a given relative stack frame, in a given index. (Store Relative)
                    //USE: STORER stack index
                    //e.g. STORER 1 0 stores the top value on the stack in slot 0 of the data stack frame above the current one.
                    var rel = codeStore[cp++];
                    var ind = codeStore[cp++];
                    var val = data.current()
                                  .pop();

                    data.relative(rel)
                        .setVar(ind, val);

                    if(debugMode) console.log("STORER: placed "+val+" in index "+ind+" of relative frame "+rel+".");
                    break;
                case opCodes.LOADR:
                    //Load a value from a relative stack frame, from a given index. (Load Relative)
                    //USE: LOADR stack index
                    //e.g. LOADR 2 0 loads the value in slot 0 of the data stack frame 2 layers above the current one.
                    var rel = codeStore[cp++];
                    var ind = codeStore[cp++];    
                    var val = data.relative(rel)
                                  .getVar(ind);

                    data.current()
                        .push(val);

                    if(debugMode) console.log("LOADR: retrieved "+val+" from index "+ind+" of relative frame "+rel+".");
                    break;
                case opCodes.POPSC:
                    //Pop off and discard the current stack data frame, equivalent to leaving a code block. (Pop Scope)
                    //USE: POPSC
                    if(debugMode){
                        console.log("POPSC: exiting scope:");
                        console.log(data.current());
                    }
                    data.exit();

                    if(debugMode) console.log("POPSC: exited current block level.");
                    break;
                case opCodes.PUSHSC:
                    //Create and push a new stack data frame, equivalent to entering a code block. (Push Scope)
                    //USE: PUSHSC
                    data.enter();

                    if(debugMode) console.log("PUSHSC: entered new block level.");
                    break;

                //BOOLEAN OPERANDS
                case opCodes.BAND:
                    //Pop two values off the stack, push A && B.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();

                    var result = (j && i);
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("BAND: " + j + " && " + i + " = " + result);
                    break;
                case opCodes.BOR:
                    //Pop two values off the stack, push A || B.
                    var i = data.current()
                            .pop();
                    var j = data.current()
                            .pop();

                    var result = (j || i);
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("BOR: " + j + " || " + i + " = " + result);
                    break;
                case opCodes.BNEG:
                    //Pop one value off the stack, push !A.
                    var i = data.current()
                                .pop();

                    var result = !i;
                    
                    data.current()
                        .push(result);

                    if(debugMode) console.log("BNEG: !" + i + " = " + result);
                    break;
                case opCodes.AGGR:
                    //Aggregate a set of elements from the stack into an array.
                    //USE: AGGR num
                    //e.g. AGGR 3 pops 3 elements a, b, anc c from the stack and pushes [a,b,c] to the stack.
                    var num = codeStore[cp++];
                    var out = [];

                    while(num-- > 0){
                        var i = data.current()
                                    .pop();
                        out.unshift(i);
                    }

                    data.current()
                        .push(out);

                    if(debugMode) console.log("AGGR: output " + out);
                    break;
                case opCodes.WIDTH:
                    //Push the width of the canvas onto the stack. Since this can't be gleaned normally.
                    data.current()
                        .push(glctx.canvas.width);

                    if(debugMode) console.log("WIDTH: " + glctx.canvas.width);
                    break;
                case opCodes.HEIGHT:
                    //Push the height of the canvas onto the stack. Since this can't be gleaned normally.
                    data.current()
                        .push(glctx.canvas.height);

                    if(debugMode) console.log("WIDTH: " + glctx.canvas.height);
                    break;
                case opCodes.AUGPT:
                    //Pop two structs off the stack, identify which is the point and append it to the line/poly.
                    var i = data.current()
                                .pop();
                    var j = data.current()
                                .pop();

                    var out;

                    if(i.length>j.length){
                        out = i.concat(j);
                    } else{
                        out = j.concat(i);
                    }

                    data.current()
                        .push(out);

                    if(debugMode) console.log("AUGPT: " +i+ " + " +j+ " = " +out);
                    break;
                case opCodes.SETCOLOUR:
                    //Pop one line/poly and one point, use the point to define the colour for that shape...
                    var colour = data.current()
                                .pop();
                    var shape = data.current()
                                .pop();

                    if(colour.size === 3){
                        colour[3] = 1;
                    }

                    for(var i = 0; i< colour.length; i++){
                        shape[i] = colour[i];
                    }

                    data.current()
                        .push(shape);

                    if(debugMode) console.log("SETCOLOUR: " +shape+ " ~ " +colour+ " = " +out);
                    break;
                case opCodes.TRANSLATEPT:
                    //Pop off twom points, move top of stack by the necessary distance.
                    var vectr = data.current()
                                    .pop();
                    var point = data.current()
                                    .pop();

                    var out = point.slice();

                    for (var i = 0; i < vectr.length; i++) {
                        out[i] += vectr[i];
                    };

                    data.current()
                        .push(out);

                    if(debugMode) console.log("TRASLATEPT: [" +point+ "] -> [" +vectr+ "] = [" +out+"]");
                    break;
                case opCodes.TRANSLATESTRUCT:
                    //Pop off twom points, move top of stack by the necessary distance.
                    var vectr = data.current()
                                    .pop();
                    var strct = data.current()
                                    .pop();

                    var out = strct.slice();

                    // alert("["+out+"]");

                    for (var i = 4; i < strct.length; i++) {
                        out[i] += vectr[i%2];
                    };

                    data.current()
                        .push(out);

                    if(debugMode) console.log("TRASLATESTRUCT: [" +strct+ "] -> [" +vectr+ "] = [" +out+"]");
                    break;
            }
            // remove garbage from stack
            //dataStore.splice(sp,dataStore.length - sp);
            //if(debugMode) console.log(JSON.stringify(dataStore));
        }
        if (needsUpdate) {render();}
        return data;
    };

    // Passes control to the browser to update the canvas and
    // requests a call back to start interpreting once the rendering has
    // complete
    render = function() {
        needsUpdate = 0;
        window.requestAnimationFrame(window.mvm.interpret);
    }

    this.call = function(address, args){
        var returnAddress = codeStore.length;
        for (var i = 0; i < args.length; i++) {
            data.current()
                .push(args[i]);
        };
        data.call(args.length, 0, returnAddress);
        // data.funcreturn();
        cp = address;

        return this.interpret();
    };

    this.kill = function(){
        this.dead = true;
    };

    // angle parameter in deegrees
    function rotatePoint(pivot, point, angle) {
        // Get origin x, y
        var pivx = pivot[0];
        var pivy = pivot[1];
        // Get point x, y
        var ptx = point[0];
        var pty = point[1];
        // Get sin and cos of angle
        var s = Math.sin((angle) * (Math.PI/180));
        var c = Math.cos((angle) * (Math.PI/180));
        // Translate point back to origin
        ptx -= pivx;
        pty -= pivy;
        // Rotate point
        var newx = ptx * c - pty * s;
        var newy = ptx * s + pty * c;
        // Translate new point back
        newx += pivx;
        newy += pivy;
        // Create new point
        var newPt = [newx,newy];
        return newPt;
    }
}

MVM.opCodes = {
    STOREG: 0,
    LOADG:     1,
    STOREL: 2,
    LOADL:     3,
    LOADC:     4,
    IADD:     5,
    ISUB:     6,
    IMUL:     7,
    IDIV:     8,
    IMOD:     9,
    FADD:     10,
    FSUB:     11,
    FMUL:     12,
    FDIV:     13,
    FMOD:     14,
    LOADIDX:15,
    SETIDX: 16,
    CMPEQ: 17,
    CMPLT: 18,
    CMPGT: 19,
    JUMP:     20,
    JUMPT:     21, 
    JUMPF:     22,
    CALL:     23, 
    RETURN: 24,
    LNDRAW: 25,
    PGDRAW: 26,
    RENDER: 27,
    CLEAR:     28,
    PTADD:     29,
    LNTOPG: 30,
    LNMUL:  31,
    EXIT:     32,
    STORER: 33,
    LOADR:    34,
    POPSC:    35,
    PUSHSC:    36,
    RETURNVAL: 37,
    BAND:    38,
    BOR:    39,
    BNEG:    40,
    AGGR:     41,
    WIDTH:    42,
    HEIGHT:    43,
    AUGPT:    44,
    SETCOLOUR:    45,
    TRANSLATEPT:    46, 
    TRANSLATESTRUCT:    47
};