How to reference grid.js variables to use other paper.js functions on grid [SOLVED]

I'm trying to do some funky things with the Quest 5.8 map by incorporating more of the paper.js functionality (curves, animations, etc.), but I've hit a roadblock.

I'd like to try to leverage as much of the existing functionality already provided by the built-in grid.js as I can, such as having anything I render drag and move when the player does. To do this, I need to be able to reference (and directly edit) the variables within grid.js, but unlike playercore.js and the other built-in javascript files, it seems like grid.js is loaded in some strange way such that I can't for the life of me figure out how to reference any of its variables from my own custom javascript files...

I've figured out how to draw custom objects to the map using paper.js javascript functions, but they will not move or drag with the map. (They will zoom, oddly enough, I suspect because the zoom functions in grid.js were all put in a gridApi container, but sadly the drag functions were not)...

While I've gotten pretty good at manipulating the Quest .aslx code, I'm still a novice when it comes to javascript so maybe there's something I'm missing? Anyone with more Javascript experience or Quest map experience have any ideas?

The built-in grid.js for reference:


var scale, gridX, gridY, player,
    playerVector, playerDestination,
    offsetVector, offsetDestination,
    allPaths = [],
    customLayerPaths = [],
    customLayerObjects = {},
    customLayerSvg = {},
    customLayerImages = {},
    layers = [],
    maxLayer = 7,
    currentLayer = 0,
    offset = new Point(0, 0),
    symbols = {},
    newShapePoints = [];

for (var i = -maxLayer; i <= maxLayer; i++) {
    var layer = new Layer();
    layers.push(project.activeLayer);
}

var customLayer = new Layer(),
    customLayerOffset = new Point(0, 0);

customLayer.visible = false;

function activateLayer(index) {
    showCustomLayer(false);
    layers[getLayerIndex(index)].activate();
    layers[getLayerIndex(index)].opacity = 1;
    if (currentLayer != index) {
        layers[getLayerIndex(currentLayer)].opacity = 0.2;
        currentLayer = index;
    }
}

function getLayerIndex(index) {
    if (index < -maxLayer || index > maxLayer) {
        alert("Layer out of bounds. Current layer range: -" + maxLayer + " to " + maxLayer);
    }
    // layers array represents z-indexes from -maxLayer to maxLayer
    return index + maxLayer;
}

activateLayer(currentLayer);

gridApi.setScale = function(newScale) {
    scale = newScale;
    gridX = new Point(scale, 0);
    gridY = new Point(0, scale);
};

gridApi.setZoom = function(zoom) {
    paper.view.zoom = zoom;
};

gridApi.zoomIn = function(amount) {
    zoom = paper.view.zoom * (Math.pow(1.1, amount));
    if (zoom > 0.0001) {
        paper.view.zoom = zoom;
    }
};

function onMouseDrag(event) {
    updateOffset(event.delta);
}


function onMouseUp(event) {
    if (_respondToGridClicks) {
        var x = getGridSquareX(event.point);
        var y = getGridSquareY(event.point);
        ASLEvent("JS_GridSquareClick", x + ";" + y);
    }
}

function updateOffset(delta) {
    setOffset(getOffset() + delta);
    var paths;
    if (project.activeLayer == customLayer) {
        paths = customLayerPaths;
    }
    else {
        paths = allPaths;
    }
    for (var i = 0; i < paths.length; i++) {
        paths[i].position += delta;
    }
    if (playerDestination && project.activeLayer != customLayer) {
        playerDestination += delta;
    }
}

function getOffset() {
    if (project.activeLayer == customLayer) {
        return customLayerOffset;
    }
    return offset;
}

function setOffset(value) {
    if (project.activeLayer == customLayer) {
        customLayerOffset = value;
    }
    else {
        offset = value;
    }
}

function onFrame(event) {
    if (playerVector) {
        var distance = player.position - playerDestination;
        if (distance.length > playerVector.length) {
            player.position += playerVector;
        }
        else {
            player.position = playerDestination;
            playerVector = null;
            playerDestination = null;

            var playerPositionAbsolute = player.position - offset;
            offsetDestination = paper.view.center - playerPositionAbsolute;

            offsetVector = (offsetDestination-offset) / 10;
        }
    }
    if (offsetVector) {
        var distance = offset - offsetDestination;
        if (distance.length > offsetVector.length) {
            updateOffset(offsetVector);
        }
        else {
            updateOffset(offsetDestination-offset);
            offsetVector = null;
            offsetDestination = null;
        }
    }
}

gridApi.drawGrid = function(minX, minY, maxX, maxY, border) {

    function gridLine(start, end) {
        var path = new Path();
        path.strokeColor = border;
        path.add(start, end);
        addPathToCurrentLayerList(path);
    }

    // draw the vertical lines
    for (var x = minX; x <= maxX; x++) {
        var start = gridPoint(x, minY);
        var end = gridPoint(x, maxY);
        gridLine(start, end);
    }

    // draw the horizontal lines
    for (var y = minY; y <= maxY; y++) {
        var start = gridPoint(minX, y);
        var end = gridPoint(maxX, y);
        gridLine(start, end);
    }
};

function gridPoint(x, y) {
    return (gridX * x) + (gridY * y) + getOffset();
}

function getGridSquareX(point) {
    return Math.floor(((point - getOffset()) / gridX).x);
}

function getGridSquareY(point) {
    return Math.floor(((point - getOffset()) / gridY).y);
}

function gridPointNudge(x, y, nudgeX, nudgeY) {
    var result = gridPoint(x, y);
    result.x += nudgeX;
    result.y += nudgeY;
    return result;
}

var firstBox = true;

gridApi.drawBox = function(x, y, z, width, height, border, borderWidth, fill, sides) {
    activateLayer(z);
    // if this is the very first room, centre the canvas by updating the offset
    if (firstBox) {
        var centrePoint = gridPoint(x + width / 2, y + height / 2);
        var offsetX = paper.view.center.x - centrePoint.x;
        var offsetY = paper.view.center.y - centrePoint.y;
        updateOffset(new Point(offsetX, offsetY));
        firstBox = false;
    }
    var path = null;
    var points = [gridPoint(x, y), gridPoint(x + width, y), gridPoint(x + width, y + height), gridPoint(x, y + height)];
    // sides is encoded with bits to represent NESW
    var draw = [sides & 8, sides & 4, sides & 2, sides & 1];
    for (var i = 0; i < 4; i++) {
        var next = (i + 1) % 4;
        if (draw[i]) {
            if (path == null) {
                path = new Path();
                allPaths.push(path);
                if (borderWidth > 0) {
                    path.strokeColor = border;
                    path.strokeWidth = borderWidth;
                }
                path.add(points[i]);
            }
            path.add(points[next]);
        } else {
            path = null;
        }
    }
    var fillPath;
    if (sides == 15) {
        fillPath = path;
    } else {
        fillPath = new Path();
        fillPath.add(points[0], points[1], points[2], points[3]);
        allPaths.push(fillPath);
    }
    fillPath.fillColor = fill;
    fillPath.closed = true;
};

gridApi.drawLine = function(x1, y1, x2, y2, border, borderWidth) {
    var path = new Path;
    path.strokeColor = border;
    path.strokeWidth = borderWidth;
    path.add(gridPoint(x1, y1));
    path.add(gridPoint(x2, y2));
    addPathToCurrentLayerList(path);
};

gridApi.drawArrow = function (id, x1, y1, x2, y2, border, borderWidth) {
    clearExistingObject(id);
    
    var linePath = new Path;
    var start = gridPoint(x1, y1);
    var end = gridPoint(x2, y2);
    linePath.strokeColor = border;
    linePath.strokeWidth = borderWidth;
    linePath.add(start);
    linePath.add(end);
    addPathToCurrentLayerList(linePath);

    var vector = end - start;
    var arrowVector = vector.normalize(10);
    var arrowheadPath = new Path([
        end + arrowVector.rotate(150),
        end,
        end + arrowVector.rotate(-150)
    ]);
    arrowheadPath.strokeColor = border;
    arrowheadPath.strokeWidth = borderWidth;
    addPathToCurrentLayerList(arrowheadPath);

    customLayerObjects[id] = [linePath, arrowheadPath];
};

function addPathToCurrentLayerList(path) {
    if (project.activeLayer == customLayer) {
        customLayerPaths.push(path);
    }
    else {
        allPaths.push(path);
    }
}

gridApi.drawPlayer = function(x, y, z, radius, border, borderWidth, fill) {
    activateLayer(z);
    if (!player) {
        player = new Path.Circle(gridPoint(x, y), radius);
        player.strokeColor = border;
        player.strokeWidth = borderWidth;
        player.fillColor = fill;
        player.fillColor = fill;
        allPaths.push(player);

        var playerPositionAbsolute = player.position - offset;
        var offsetDestinationX = paper.view.center.x - playerPositionAbsolute.x;
        var offsetDestinationY = paper.view.center.y - playerPositionAbsolute.y;

        offsetDestination = new Point(offsetDestinationX, offsetDestinationY);
        offsetVector = (offsetDestination - offset);
    } else {
        playerDestination = gridPoint(x, y);
        playerVector = (playerDestination - player.position) / 10;
        // move player to the end of the activeLayer so it gets drawn on top
        project.activeLayer.addChild(player);
    }
};

 
gridApi.drawLabel = function(x, y, z, text, col) {
    if (col === undefined) col = "black";
    activateLayer(z);
    var pointText = new PointText(gridPoint(x, y));
    pointText.justification = "center";
    pointText.fillColor = col;
    pointText.content = text;
    allPaths.push(pointText);
};

function showCustomLayer(visible) {
    if (visible != customLayer.visible) {
        customLayer.visible = visible;
        for (var idx = 0; idx < layers.length; idx++) {
            layers[idx].visible = !visible;
        }
        if (visible) {
            customLayer.activate();
        }
        else {
            layers[getLayerIndex(currentLayer)].activate();
        }
    }
}

gridApi.showCustomLayer = function(visible) {
    showCustomLayer(visible);
};

gridApi.clearCustomLayer = function() {
    customLayer.removeChildren();
};

gridApi.clearAllLayers = function () {
    player = null;
    $.each(layers, function(idx, layer) {
        layer.removeChildren();
    });
};

gridApi.setCentre = function(x, y) {
    var centrePoint = gridPoint(x, y);
    var offsetX = paper.view.center.x - centrePoint.x;
    var offsetY = paper.view.center.y - centrePoint.y;
    var curOffset = getOffset();
    updateOffset(new Point(offsetX, offsetY));
};

gridApi.drawCustomLayerSquare = function(id, x, y, width, height, text, fill) {
    var points = [];
    points.push(gridPointNudge(x, y, 1, 1));
    points.push(gridPointNudge(x + width, y, -1, 1));
    points.push(gridPointNudge(x + width, y + height, -1, -1));
    points.push(gridPointNudge(x, y + height, 1, -1));

    var textPoint = gridPoint(x + width / 2, y + height / 2);
    gridApi.drawCustomLayerObject(id, points, text, textPoint, fill, fill);
};

function clearExistingObject(id) {
    var existing = customLayerObjects[id];
    if (existing) {
        for (var idx in existing) {
            var path = existing[idx];
            // TO DO: Should remove path from layer and layerlist array
            path.visible = false;
        }
    }
}

gridApi.drawCustomLayerObject = function (id, points, text, textPoint, border, fill, opacity) {
    clearExistingObject(id);

    var paths = new Array();
    path = new Path();
    path.strokeColor = border;
    $.each(points, function(index, value) {
        path.add(value);
    });
    path.fillColor = fill;
    path.closed = true;
    if (typeof opacity != "undefined") {
        path.opacity = opacity;
    }
    addPathToCurrentLayerList(path);
    paths.push(path);

    if (text) {
        var pointText = new PointText(textPoint);
        pointText.justification = "center";
        pointText.fillColor = "black";
        pointText.content = text;
        if (typeof opacity != "undefined") {
            pointText.opacity = opacity;
        }
        addPathToCurrentLayerList(pointText);
        paths.push(pointText);
    }

    customLayerObjects[id] = paths;
};

gridApi.loadSvg = function (data, id) {
    var svg = paper.project.importSVG(data);
    if (svg) {
        symbols[id] = new Symbol(svg);
    }
};

gridApi.drawCustomLayerSvg = function (id, symbolId, x, y, width, height) {
    if (symbolId in symbols) {
        var existing = customLayerSvg[id];
        var placedSymbol = existing ? existing : symbols[symbolId].place();
        placedSymbol.scale(gridX.x * width / placedSymbol.bounds.width, gridY.y * height / placedSymbol.bounds.height);
        placedSymbol.position = gridPoint(x, y) + placedSymbol.bounds.size / 2;
        if (!existing) addPathToCurrentLayerList(placedSymbol);
        customLayerSvg[id] = placedSymbol;
    } else {
        console.log("No symbol loaded with id '" + symbolId + "'");
    }
};

gridApi.drawCustomLayerImage = function(id, url, x, y, width, height) {
    var existing = customLayerImages[id];
    var raster = existing ? existing : new Raster(url);
    var resizeRaster = function() {
        raster.scale(gridX.x * width / raster.bounds.width, gridY.y * height / raster.bounds.height);
        raster.position = gridPoint(x, y) + raster.bounds.size / 2;
    };
    if (existing) {
        resizeRaster();
    } else {
        raster.onLoad = resizeRaster;
        addPathToCurrentLayerList(raster);
        customLayerImages[id] = raster;
    }    
}

gridApi.addNewShapePoint = function (x, y) {
    newShapePoints.push([x, y]);
};

gridApi.drawShape = function (id, border, fill, opacity) {
    var points = [];
    for (var idx in newShapePoints) {
        var xy = newShapePoints[idx];
        points.push(gridPoint(xy[0], xy[1]));
    }
    gridApi.drawCustomLayerObject(id, points, null, null, border, fill, opacity);
    newShapePoints = [];
};

gridApi.onLoad();



My above question still stands (and would resolve a great deal of headaches if I could figure it out), but in case it isn't possible after all, I've decided to try an alternative approach in the mean time.

Since I've already worked out how to draw to the map grid manually with paper.js commands, I figure that might be good enough as long as I can manually "mimic" the existing grid behaviors as well:

  • zooming
  • dragging
  • moving rendered objects when player changes rooms

As mentioned before, zooming is working right out of the box, so that's good to go. To get dragging to work for my custom drawings like it does for the rest of the map, I tried the following javascript:

// Javascript
// Draw a custom box to test dragging 

var point = new paper.Point(20, 20);
var size = new paper.Size(60, 60);
var customShape = new paper.Shape.Rectangle(point, size);
customShape.strokeColor = 'black'; 

// Dragging event handler... 

paper.project.view.onMouseDrag = function(event) {
    customShape.position += event.delta;
} 

As far as I can tell, this should be working (it works on the paper.js website perfectly), but for some reason it doesn't work when I run the Javascript in Quest... Anyone know why that might be the case?


Never figured out how to directly reference the grid.js variables, but I DID figure out a way to use expanded paper.js functions to make unique shapes/animation in the map grid that ALSO follow the same rules as the other built-in grid-drawings (i.e. they will properly pan when the player moves, drags the map, zooms in and out, etc.). I even found a way to make custom shapes obey the z-layer (going see-through when the player changes level)!

The trick was to create a "seed" shape using the built-in Quest functionality so the Path would get logged in the internal grid.js functions. Then, after that seed shape is made, I can go in with Javascript to locate and modify that shape into whatever I want. Like so...

// In Quest...
// Create reference path for javascript to find. 
// x and y are the desired origin of the shape, in Quest coordinates, though these can be changed later if you like. 
// The z coordinate is the level you want the shape to be on so it goes solid/see-through when the player moves up and down on the map.
// IMPORTANT: I give the reference path a strokeColor of "#010203" and a fillColor of "#030201" so it can be found later by JS.drawCustomBlob. I chose these colors because they are unique enough to be unlikely to occur by accident.
JS.Grid_DrawBox (x, y, z, 0, 0, "#010203", 1, "#030201", 15)
// Call your own custom JS.drawCustomBlob function...
JS.drawCustomBlob ()

Then, in Javascript, I have the following...

// In Javascript...
paper.install(window);

// 1 room unit in Quest grid = 30 units in paper.js. So scale=30...
var scale = 30;

function findRefPath () {
  // Finds and returns the reference path in the grid made by quest so it can be altered by custom functions
  // Assumes customPath will have strokeColor of the unique color rgb(1,2,3) (HEX #010203) and fillColor of the unique color rgb(3,2,1) (HEX #030201)
  var children = paper.project.activeLayer.children;
  // Iterate through the items contained within the array:
  for (var i = 0; i < children.length; i++) {
    var child = children[i];
    if (typeof child.strokeColor !== 'undefined' && typeof child.fillColor !== 'undefined') {
      //console.log("index = "+i.toString())
      if (child.strokeColor !== null && child.fillColor !== null) {
        if (child.strokeColor.toCSS()=='rgb(1,2,3)' && child.fillColor.toCSS()=='rgb(3,2,1)') {
          child.strokeColor = null;
          child.fillColor = null;
           // To find refPath
           var Index = i
           //console.log ("refPath Index (paper.project.activeLayer.children[i])="+Index.toString())
           return Index
        }
      }
    }
  }
  // If not found within For loop, return error to console...
  console.log ("findRefPath() ERROR: Unable to find reference path of rgb(1,2,3)");
}

function drawCustomBlob () {
  // Find and remove CustomBlob from the grid if it already exists so new one can be drawn if Quest function called again
  for (var i = 0; i < paper.project.layers.length; i++) {
    if (typeof paper.project.layers[i].children['CustomBlob'] !== 'undefined') {
      paper.project.layers[i].children['CustomBlob'].remove();
    }
  }
  var refIndex = findRefPath();
  var myPath = paper.project.activeLayer.children[refIndex]
  // Use refPath point as origin
  var originX = myPath.position.x;
  var originY = myPath.position.y;
  // Clear existing path data
  myPath.removeSegments();
  myPath.name = "CustomBlob";
  //Top Right Curve
  myPath.add(new paper.Segment(new paper.Point(-4.0*scale+originX, -6.0*scale+originY), null, new paper.Point(2.0*scale, 0*scale)));
  myPath.add(new paper.Segment(new paper.Point(.5*scale+originX, -6.0*scale+originY), new paper.Point(0*scale, -2.0*scale), null));
  //Bottom Right Curve
  myPath.add(new paper.Segment(new paper.Point(.5*scale+originX, 4.5*scale+originY), null, new paper.Point(0*scale, 2.0*scale)));
  myPath.add(new paper.Segment(new paper.Point(-4.0*scale+originX, 7.0*scale+originY), new paper.Point(2.0*scale, 0*scale), null));
  //Bottom Left Curve
  myPath.add(new paper.Segment(new paper.Point(-4.0*scale+originX, 7.0*scale+originY), null, new paper.Point(-1.5*scale, 0*scale)));
  myPath.add(new paper.Segment(new paper.Point(-9.0*scale+originX, 4.5*scale+originY), new paper.Point(0*scale, 1.5*scale), null));
  //Top Left Curve
  myPath.add(new paper.Segment(new paper.Point(-9.0*scale+originX, -3.0*scale+originY), null, new paper.Point(0*scale, -1.5*scale)));
  myPath.add(new paper.Segment(new paper.Point(-6.0*scale+originX, -6.0*scale+originY), new paper.Point(-1.5*scale, 0*scale), null));
  myPath.closed = true;

  myPath.fillColor = 'yellow';
  myPath.strokeColor = 'black';
  myPath.strokeWidth = 1;
  myPath.opacity = 1.0;
}

You can do this to make any shape you want in the map grid using the Javascript functions defined by paper.js. References and tutorials for paper.js functions can be found here: http://paperjs.org/tutorials/. Keep in mind the paper.js version built-in to Quest 5.8 is pretty old (v0.9.12) compared to the latest version on the paper.js website, so not all functions will work, but I've so far found that most have worked for my needs.

Hopefully someone else finds this useful too


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums