I am trying to build a small CAD app using HTML5 Canvas and Paper.js
Now I am stuck at a problem I'd hate asking for help for since I believe I should solve this on my own, but everytime I try to think about a solution my head hurts.
I am trying to build the following functions:
Align-left
Align-right
Align-center.
The user is supposed to select a bunch of elements on the canvas and click one of the above functions to align them together.
The whole concept is that the user selects a Reference Item in a way. The elements are aligned relative-to the Reference Item. This ensures that the alignments happen in a user predictable way.
There are 2 ways for the user to select elements
Selection-via-Intersection
The user clicks and drags a selection rectangle. While dragging I
can track in an array which items intersected with the selection
rectangle. I can track the first intersected item here just fine, so I
have a Reference Item .Works perfectly so far.
Selection-via-Click+Shift
The user holds Shift and clicks on elements to do a multi-selection.
It's a functionality I'm sure everyone has seen in any modern graphics
editor. I have yet to figure out a way how to track a first-selected
item here, since items can be set as first-selected and then
deselected.
The problem:
The above selection methods can be COMBINED together -
The user can select-via-intersection and then start select/de-select items using Shift+Click.
Which one from the selectedItems is the Reference-Item now?
An example:
Item 2 was the first-selection on selection-via-intersection. Item 2
was de-selected using Shift+Click. Which one is the Reference-Item
now?.
Here is a GIF that illustrates what I am talking about (although it's invinsible in the GIF the items get selected as soon as the intersection rectangle touches them.)
I am aware that the whole thing will involve some array logic but I cannot seem to grasp how to implement the whole thing.
Notes:
Apart from the complexity of this thing, I have performance constraints as well. Intersection Detection which is used for the Selection-via-Intersection is already a heavy operation and it get's fired continously while the user drags a selection rectangle.
This is the code for Select-Via-Intersection
The following function getSortedIntersects get's called continuously while the user drags a selection rectangle. I log in the console the first selected item:
var checkedSegments = new Array();
function getSortedIntersects(rect) {
//children are all the items drawn on the canvas
var children = project.activeLayer.children;
//I clone the children array into another array and I exclude all items other than Paths
//Intersection Detection works only on paths.
var pathsOnCanvas = children.filter(function (el) {
return el instanceof Path
});
var newCheckedSegments = new Array();
var firstSelection = "";
//Start looping over all Paths on the canvas
for (var i = 0; i < pathsOnCanvas.length; i++) {
//create an array which lists all the intersections. The array is called ''intersections''
var intersections = pathsOnCanvas[i].getIntersections(rect);
//If there is at least 1 intersection OR the rect contains a path then proceed
if (intersections.length > 0 || rect.contains(pathsOnCanvas[i].bounds)) {
//Checks if the selected item at index i is in the newSegments array. If so, it continues iterating the loop without going further at this iteration.
if (newCheckedSegments.indexOf(pathsOnCanvas[i]) >= 0)
continue;
pathsOnCanvas[i].selected = true;
newCheckedSegments.push(pathsOnCanvas[i]);
}
}
for (var i = 0; i < newCheckedSegments.length; i++) {
if (checkedSegments.indexOf(newCheckedSegments[i]) < 0)
checkedSegments.push(newCheckedSegments[i]);
firstSelection = (checkedSegments[0].id);
console.log("first selection is " + firstSelection);
}
for (var i = checkedSegments.length - 1; i >= 0; i--) {
if (newCheckedSegments.indexOf(checkedSegments[i]) < 0) {
checkedSegments[i].selected = false;
checkedSegments.splice(i, 1);
}
}
}
Related
Firstly - I am a novice who has picked away at getting something functional for me over the past few years. My code below basically works - it's just complex and I believe there must be a better approach. Looking for a kick in different direction.
I am using javascript and leaflet to display an array of moving markers (players). The data on the markers comes to me in a table - listing an ID for each marker, a location and other information not needed for this purpose. The table of data will have a changing number of markers (ie: just one (MYID), or that plus one other, two or even more 'others'), and it will have multiple entries for the each marker (same ID and location, just the other data is different).
One of the markers ids is the same as "MYID" which I then treat differently, but for the others (if any) I have a function that puts them on my map. And each time the function is called it should move them to their new location data, but I currently don't know how to do that elegantly. So I am presently looking to delete all and recreate them each time the function is called (triggered by each time anything changes). Not so good coding.
function updatemap(displaytable,MYID) { // Update other player locations on map
OPN=[]; //reset OPN array
OPX=[]; //reset OPX array
OPY=[]; //reset OPY array
for (var r=0;r<OPLoc.length;r++){
// Need to remove all OPloc off map first and then re-create them?
};
OPLoc=[];
var q=0;
for (var p=0; p<displaytable.length; p++){ // for every line of the displaytable
if ((!OPN.includes(displaytable[p].id)) && (displaytable[p].id != MYID)){ // ... create a unique other player entry, once only
OPN.push(displaytable[p].id);
OPX.push(displaytable[p].lat);
OPY.push(displaytable[p].lon);
OPLoc[q] = new L.marker([displaytable[p].lat,displaytable[p].lon], {icon: oplayericon})
.addTo(mymap)
.bindPopup(displaytable[p].id + "<br>" + displaytable[p].lat + "," + displaytable[p].lon);
q++;
};
};
//...other code not relevant
};
None of the variable/arrays in the function are needed anywhere else for anything - I've just created all this mess to do this one thing. And the arrays are created globally so that they are available next time it's called.
I assume there are functions/capabilities of the array commands that I am just not aware of. Can anyone suggest a better way? please?
well, still not sure it's elegant - but it's functional. Not sure if there are edge cases that I'm not covering - but this appears to work and do what I need. I only use two arrays now (one for the markers and one for the ids so I can keep track of what's active). I tried making a 2D array but that made me sad, so it's two arrays now.
for (var s=0;s<displaytable.length;s++) { // display all other players
if (OPid.includes(displaytable[s].id)) { //if the player is already on the map, just update location
OPLoc[OPid.indexOf(displaytable[s].id)].setLatLng([displaytable[s].lat,displaytable[s].lon]);
} else { // if the player is not MYID and not already on the map, create it
if (displaytable[s].id != MYID) {
OPid.push(displaytable[s].id);
OPLoc[OPid.indexOf(displaytable[s].id)] = new L.marker([displaytable[s].lat,displaytable[s].lon], {icon: oplayericon})
.addTo(mymap)
.bindPopup(displaytable[s].id);
};
};
};
for (var r=0;r<OPid.length;r++) { //check if the OPlayer is still active
var found = false;
for(var q=0;q<displaytable.length;q++) {
if (displaytable[q].id == OPid[r]) {
found = true;
};
};
if (!found) { //if the OPlayer id is no longer in the displaytable
mymap.removeLayer(OPLoc[r]); //remove it from the map
OPLoc.splice(r,1); //remove it from the OPLoc array
OPid.splice(r,1); //remove it form the OPid array
};
};
```
I am struggling to randomly select some divs (9 in this case) to place bombs/mines for those selected tiles.
My code for this is:
function place_mines() {
if (document.getElementById("difficulty").selectedIndex == "2") {
for (var u = 0; u < 8; u++) {
}
}
}
Inspected the element, grid which contains all the tiles.
I would use a different approach. I would make a list of tile objects and generate the html from the list. This lets you flag an object as bomb without revealing it to the user by adding it as class name.
After you generated the list, you can take a random subset and flag it as bombs. You could use this approach for example: Sampling a random subset from an array.
I suggest you to give id to the tiles, to later you generate random id , and map the generated id to the tiles
What worked for me was I added a counter to index the elements.
After that, made an array of 9 random numbers from 0-80 (counter starts at 0), checked if the id of the tile selected is in that array, if it was then that tile has a mine else its a normal tile.
tile.setAttribute("id",counter);counter++;
I want to write a script that does the following:
select some textframes
center the selection horizontally
distribute the frames horizontally
the whole routine has to be done on several pages
My first problem is, that I only want to select textframes from a unlocked layer. I found several solutions where all textframes, even on locked layers, were selected.
And the only solution I found so far that just selects the textframes from my unlocked layer (app.menuActions.item("$ID/Select &All").invoke();) doesn't provide an object I can work with (adjust, distribute) afterwards.
Is there a solution to my requirements?
Edit:
My last attempt looked like this (for a single page, I didn't used a loop for several pages while testing):
// 'allPageItems' erfasst alle Rahmen, zusätzlich Gruppen und Bilder
var allObjects = app.activeDocument.layoutWindows[0].activeSpread.allPageItems;
// eine Schleife durch die Objekte
for (var n=0; n<allObjects.length; n++) {
var curObject = allObjects[n];
// prüfen, ob Textrahmen
if (curObject.constructor.name == "TextFrame") {
// verankerte Textrahmen ausschliessen
if (curObject.parent != "[object Character]") {
// zur Auswahl hinzufügen
curObject.select(SelectionOptions.ADD_TO);
}
}
}
Generally, selection is something that is intended for UI interaction, not for scripting. Therefore you should avoid handling all the selection stuff in your script and collect the textFrames as objects in an array that you then can use to do the other stuff.
This should work:
#target indesign
var doc = app.activeDocument;
var curSpread = doc.layoutWindows[0].activeSpread;
var spreadItems = curSpread.allPageItems;
var distObjects = [];
// collect all relevant objects in distObjects
for (var i = 0; i < spreadItems.length; i += 1) {
var si = spreadItems[i];
// skip if itemLayer is locked
if (si.itemLayer.locked) continue;
// skip if item is not a textFrame
if (!(si instanceof TextFrame)) continue;
// skip if item is anchored
if (si.parent.constructor.name === "Character") continue;
distObjects.push(si);
};
// group all collected objects to center them, then ungroup
var distGroup = curSpread.groups.add(distObjects);
doc.align([distGroup], AlignOptions.HORIZONTAL_CENTERS, AlignDistributeBounds.SPREAD_BOUNDS);
distGroup.ungroup();
// distribute all objects horizontally
doc.distribute(distObjects, DistributeOptions.HORIZONTAL_CENTERS, AlignDistributeBounds.ITEM_BOUNDS);
Note: If this is used with an older ID version (prior to CC2014 I believe), after ungrouping, all pageItems will stay on the same layer. The feature for them to move back to the original layer was only introduced recently. If you need a solution for an older InDesign version, you would need to calculate the bounds of the group of objects you found, then offset them all one by one, so the entire "selection" can get centered.
DISCLAIMER
I have absolutely no idea how to succinctly describe the nature of the problem I am trying to solve without going deep into context. It took me forever to even think of an appropriate title. For this reason I've found it nearly impossible to find an answer both on here and the web at large that will assist me. It's possible my question can be distilled down into something simple which does already have an answer on here. If this is the case I apologise for the elaborate duplicate
TL;DR
I have two arrays: a main array members and a destination array neighbours (technically many destination arrays but this is the tl;dr). The main array is a property of my custom group object which is auto-populated with custom ball objects. The destination array is a property of my custom ball object. I need to scan each element inside of the members array and calculate distance between that element and every other element in the members group. If there exist other elements within a set distance of the current element then these other elements need to be copied into the current element's destination array. This detection needs to happen in realtime. When two elements become close enough to be neighbours they are added to their respective neighbours array. The moment they become too far apart to be considered neighbours they need to be removed from their respective neighbours array.
CONTEXT
My question is primarily regarding array iteration, comparison and manipulation but to understand my exact dilemma I need to provide some context. My contextual code snippets have been made as brief as possible. I am using the Phaser library for my project, but my question is not Phaser-dependent.
I have made my own object called Ball. The object code is:
Ball = function Ball(x, y, r, id) {
this.position = new Vector(x, y); //pseudocode Phaser replacement
this.size = r;
this.id = id;
this.PERCEPTION = 100;
this.neighbours = []; //the destination array this question is about
}
All of my Ball objects (so far) reside in a group. I have created a BallGroup object to place them in. The relevant BallGroup code is:
BallGroup = function BallGroup(n) { //create n amount of Balls
this.members = []; //the main array I need to iterate over
/*fill the array with n amount of balls upon group creation*/
for (i = 0; i < n; i++) {
/*code for x, y, r, id generation not included for brevity*/
this.members.push(new Ball(_x, _y, _r, _i)
}
}
I can create a group of 4 Ball objects with the following:
group = new BallGroup(4);
This works well and with the Phaser code I haven't included I can click/drag/move each Ball. I also have some Phaser.utils.debug.text(...) code which displays the distance between each Ball in an easy to read 4x4 table (with duplicates of course as distance Ball0->Ball3 is the same as distance Ball3->Ball0). For the text overlay I calculate the distance with a nested for loop:
for (a = 0; a < group.members.length; a++) {
for (b = 0; b < group.members.length; b++) {
distance = Math.floor(Math.sqrt(Math.pow(Math.abs(group.members[a].x - group.members[b].x), 2) + Math.pow(Math.abs(group.members[a].y - group.members[b].y), 2)));
//Phaser text code
}
}
Now to the core of my problem. Each Ball has a range of detection PERCEPTION = 100. I need to iterate over every group.members element and calculate the distance between that element (group.members[a]) and every other element within the group.members array (this calculation I can do). The problem I have is I cannot then copy those elements whose distance to group.members[a] is < PERCEPTION into the group.members[a].neighbours array.
The reason I have my main array (BallGroup.members) inside one object and my destination array inside a different object (Ball.neighbours) is because I need each Ball within a BallGroup to be aware of it's own neighbours without caring for what the neighbours are for every other Ball within the BallGroup. However, I believe that the fact these two arrays (main and destination) are within different objects is why I am having so much difficulty.
But there is a catch. This detection needs to happen in realtime and when two Balls are no longer within the PERCEPTION range they must then be removed from their respective neighbours array.
EXAMPLE
group.members[0] -> no neighbours
group.members[1] -> in range of [2] and [3]
group.members[2] -> in range of [1] only
group.members[3] -> in range of [1] only
//I would then expect group.members[1].neighbours to be an array with two entries,
//and both group.members[2].neighbours and group.members[3].neighbours to each
//have the one entry. group.members[0].neighbours would be empty
I drag group.members[2] and group.members[3] away to a corner by themselves
group.members[0] -> no neighbours
group.members[1] -> no neighbours
group.members[2] -> in range of [3] only
group.members[3] -> in range of [2] only
//I would then expect group.members[2].neighbours and group.members[3].neighbours
//to be arrays with one entry. group.members[1] would change to have zero entries
WHAT I'VE TRIED
I've tried enough things to confuse any person, which is why I'm coming here for help. I first tried complex nested for loops and if/else statements. This resulted in neighbours being infinitely added and started to become too complex for me to keep track of.
I looked into Array.forEach and Array.filter. I couldn't figure out if forEach could be used for what I needed and I got very excited learning about what filter does (return an array of elements that match a condition). When using Array.filter it either gives the Ball object zero neighbours or includes every other Ball as a neighbour regardless of distance (I can't figure out why it does what it does, but it definitely isn't what I need it to do). At the time of writing this question my current code for detecting neighbours is this:
BallGroup = function BallGroup(n) {
this.members = []; //the main array I need to iterate over
//other BallGroup code here
this.step = function step() { //this function will run once per frame
for (a = 0; a < this.members.length; a++) { //members[a] to be current element
for (b = 0; b < this.members.length; b++) { //members[b] to be all other elements
if (a != b) { //make sure the same element isn't being compared against itself
var distance = Math.sqrt(Math.pow(Math.abs(this.members[a].x - this.members[b].x), 2) + Math.pow(Math.abs(this.members[a].y - this.members[b].y), 2));
function getNeighbour(element, index, array) {
if (distance < element.PERCEPTION) {
return true;
}
}
this.members[a].neighbours = this.members.filter(getNeighbour);
}
}
}
}
}
I hope my problem makes sense and is explained well enough. I know exactly what I need to do in the context of my own project, but putting that into words for others to understand who have no idea about my project has been a challenge. I'm learning Javascript as I go and have been doing great so far, but this particular situation has me utterly lost. I'm in too deep, but I don't want to give up - I want to learn!
Many, many, many thanks for those who took the time read my very long post and tried provide some insight.
edit: changed a > to a <
I was learning more on object literals, I'm trying to learn JS to ween myself off of my jQuery dependency. I'm making a simple library and I made a function that adds properties of one object to another object. It's untested, but I think if you were apply something similar it might help. I'll try to find my resources. Btw, I don't have the articles on hand right now, but I recall that using new could incur complications, sorry I can't go any further than that, I'll post more info as I find it.
xObject could be the ball group
Obj2 could be the members
Obj1 could be the destination
/* augment(Obj1, Obj2) | Adds properties of Obj2 to Obj1. */
// xObject has augment() as a method called aug
var xObject = {
aug: augument
}
/* Immediately-Invoked Function Expression (IIFE) */
(function() {
var Obj1 = {},
Obj2 = {
bool: true,
num: 3,
str: "text"
}
xObject.aug(Obj1, Obj2);
}()); // invoke immediately
function augment(Obj1, Obj2) {
var prop;
for (prop in Obj2) {
if (Obj2.hasOwnProperty(prop) && !Obj1[prop]) {
Obj1[prop] = Obj2[prop];
}
}
}
I'm working on a html/javascript game for android. It's a board game which does the following.
It has tiles of different colors and user can place one tile (chosen programatically) on the board. If we get 4 or more tiles of the same color/shape we score some points and these tiles will disappear. The tiles above the removed tiles will replace them and new tiles will be added to the empty places. The image below shows how it works (this is just an example, the real board can have different dimensions):
The Tiles are <img> elements with their ids stored in an array which I use to check for matches and replacement.
It all works pretty well but once the new tiles are added to board I need to examine the whole board to check if new matches are avalable. And I want some advice here, because examining the whole board can be really slow. Is there a way I can do this efficiently?
Here's what I thought about doing:
Given the previous example,I thought about examining only the elements in the red area, i.e. only the elements that have been moved or added. It can be effective if the tiles move vertically, as I'll only have to check the moved/added tiles and it'll give me the new matches. But in case where I remove tiles horizontally it can be problematic, because if these tiles are at the bottom i'll have to examine the whole board and i confront the same problem.
Any advice or suggestion will be appreciated.
Note: I didn't add any code because it simply consists of checking the lines and columns for a given tile and look for matches. But if needed I can provide it.
EDIT: Before anyone can object I want to inform that I just added this question to Game Development section as I didn't receive any answers here :).
EDIT: Adding my code
function initializeBoard(){
//items is an array which contains tiles/images names
for(var i=0; i < totalItems; i++)
board[i+1] = Math.floor(Math.random() * (items.length - 1)) + 1;
for(var i=0; i < totalItems; i++)
{
if( !(i % numberShapesXAxis) )
document.write("<BR>");
document.write("<img src=\"images/"+ items[board[i+1]]+ ".gif\" style = \"border:0; height:"+ itemSize+ "px; width:"+ itemSize+ "px;\" name=\"t", i+1,"\" onclick = \"replaceAndCheck(", i+1, ")\"><\/a>");
}
}
//so basically board contains image ids.
How about checking if there is a new match when you move a stone. So when stone x moves down you check if the new position of X creates a match. That way you can create a recursive kind of method.