I have a class, which contains a parameter called values. This is used to save values of points that represent specific shapes on a canvas.
I need to implement a functionality that lets me drag those shapes around, so I need to modify each specific point of the shape, removing from them the amount that was dragged.
So I decided that, as I trigger my mousedown event (which is the method StartMove), i would save the values of my points on a startValues variable, and as I move my mouse around (method move), I would then update the values, using startValues and the distance between the starting point and the current mouse position to determine my new point location.
The problem is, this.startValues is actually getting changed to match this.values every time my cursor moves, and I have no idea why. Is there anything simple I'm missing?
Since I store my values as values, and not coordinates (helps me with panning and zooming on the canvas), I first convert the values to position, then modify the position and then convert it back to a value. I've included the parent class, Grf, so you can see the methods which change values to position and position to values.
Class with the problems
class Test {
constructor(grf){
this.grf = grf; // Another class, which contains important methods
this.values = [];
this.startValues = [];
}
startMove(p0){ // p0 = [x,y]
const {grf} = this;
this.startValues = [...this.values]; //I also tried this.startValues = this.values
this.p0 = p0;
grf.canvas.addEventListener('mousemove',this.move);
grf.canvas.addEventListener('mouseup', this.endMove);
}
move = (evt) => { // arrow function so 'this' is bound to Test class instead of grf.canvas
const {grf, p0, values, startValues} = this;
const coords = grf.canvas.getBoundingClientRect();
const px = evt.clientX - coords.left;
const py = evt.clientY - coords.top;
for (let i = 0, iMax = this.values.length; i < iMax; i++){
values[i][0] = grf.valX(grf.posX(startValues[0]) - (p0[0] - px));
values[i][1] = grf.valY(grf.posY(startValues[1]) - (p0[1] - py));
}
console.log(this.startValues); // It changes to the same as this.values
}
endMove = (evt) => { // arrow function so 'this' is bound to Test class instead of grf.canvas
const {grf} = this;
grf.canvas.removeEventListener('mousemove',this.move);
grf.canvas.removeEventListener('mouseup',this.endMove);
}
}
The other class
class Grf {
constructor(canvas){ // Not the actual constructor, just an example of what the values could look like
this.translateX = 1000;
this.translateY = 1000;
this.scaleY = 10.7;
this.scaleX = 11.2;
this.canvas = canvas;
}
posX (value){
return (value-this.translateX)*this.scaleX;
}
posY (value){
return (this.canvas.height-(100*(value))-this.translateY)*this.scaleY;
};
valX(pos){
return (pos/this.scaleX) + this.translateX
}
valY(pos){
return (-1)*((pos/this.scaleY) + this.translateY - this.canvas.height)/100
}
}
How values are inserted into startValues and values in Test class? You probably insert exactly the same object in both without coping it so both arrays hold the same instances.
Take a look at the example:
const obj = { a : 10 };
const a = [];
a.push(obj);
const b = [...a]; // creates new array, but with same objects
a[0].a = 20;
console.log(b[0]) // gives "{ a : 20 }"
To make it separate you need to make a copy of a object:
a.push({...obj})
Related
I am looking to connect plane buffer geometry grid tiles which have real elevation data from IndexedDB. My issue is the data resolution on the STRM elevation is not perfect so the edges between the tiles are not the same. I need to essentially average out all the grid edges between the touching vertices to create a seamless terrain.
When I copy paste the code into the console in the scene it works. However just in the code it doesn't. The sceneRef that is passed is valid and the rest of the codebase using the sceneRef correctly.
The tiles are a 3 x 3 with the current grid tile being the center at 1,1 from range 0,0 - 2,2.
function connectTiles(currGridKey, sceneRef){
console.log("connectTiles");
console.log("currGridKey");
// Current Tile Connection
for (var lat = 0; lat < currGridKey[0]+2; lat++) {
for (var long = 0; long < currGridKey[1]+2; long++) {
const currentTile = sceneRef.getObjectByName(`${lat}-${long}`);
// Current Grid Tile Per Loop
if (currentTile) {
const currentTileVerts = currentTile.geometry.attributes.position.array,
latPlusTile = sceneRef.getObjectByName(`${lat}-${long+1}`),
longPlusTile = sceneRef.getObjectByName(`${lat+1}-${long}`);
// Connect Latitudinally
if (latPlusTile) {
const latPlusTileVerts = latPlusTile.geometry.attributes.position.array;
for (var z = 0; z < currentTileVerts.length; z+=27) {
const newVertHeight = (currentTileVerts[z] + latPlusTileVerts[z]) / 2;
latPlusTileVerts[z] = newVertHeight;
currentTileVerts[z] = newVertHeight;
}
latPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
// Connection Longitudinally
if (longPlusTile) {
const longPlusTileVerts = longPlusTile.geometry.attributes.position.array;
for (var x = 0; x < currentTileVerts.length; x+=3) {
const newVertHeight = (currentTileVerts[x] + longPlusTileVerts[x]) / 2;
longPlusTileVerts[x] = newVertHeight;
currentTileVerts[x] = newVertHeight;
}
longPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
}
}
}
If all values inside the array are in fact being updated, maybe they're just not getting uploaded to the GPU. Instead of changing the value inside geometry.attributes.position directly, try using the .setAttribute() method. The docs state that using .setAttribute() and .getAttribute() is preferrable than accessing it directly because it has its own internal storage mechanism.
const latPlusTileVerts = latPlusTile.geometry.getAttribute("position").array;
// ... Loops
latPlusTile.geometry.getAttribute("position").needsUpdate = true;
// Or an alternative is to generate a new attribute...
// in case updating the old one fails
const posAttrib = new THREE.BufferAttribute(latPlusTileVerts, 3);
latPlusTile.geometry.setAttribute("position", posAttrib);
I have a game I'm making in adobe animate HTML canvas. In my code below Is there a way to combine all of the stateItems[].stateplace1 =this.state so I don't have like 50 different variations? I tried make the state1s the child of the StateItems but it still feels very derivative.
If stateItems and state1s were two different arrays is there a way to call the first item (Florida) of stateItems to the first target(state1)and so forth so they are "linked" so speak. Would something like that work and how would I go about do that?
I'm very new to javascript so my apologies if there is a super easy solution.
Edit:
The StateItems are individual symbols in Animate of the actual United States of America example: Florida is a state in the literal United States so it's part of my StateItem array. The stateplace is where the states will be placed and stateplace is one symbol and stateplace 1 2 and 3 are instances of stateplace. I just wanted to know if there was a way to cleanup the stateplace1s in the StateItems loop so there isn't 50 stateItems[0].stateplace1 = this.stateplace1;
I just couldn't figure out another way to connect each StateItem to its corresponding stateplace. The code works the way I need it to I just don't know if theres a way to clean it up a bit. I hope that clears things up.
var stateItems = [this.florida, this.alabama, this.southcarolina]
// we apply the same code for each symbol with the for loop
for(var i = 0; i<stateItems.length; i++){
stateItems[i].on("mousedown", onMouseDown.bind(this));
stateItems[i].on("pressmove", onMouseMove.bind(this));
stateItems[i].on("pressup", onMouseUp.bind(this));
stateItems[0].stateplace1 = this.stateplace1;
stateItems[1].stateplace1 = this.stateplace2;
stateItems[2].stateplace1 = this.stateplace3;
stateItems[i].originX = stateItems[i].x;
stateItems[i].originY = stateItems[i].y;
}
// mouse down event
function onMouseDown(evt){
var item = evt.currentTarget;
item.offset = {x:0, y:0};
var pt = item.parent.globalToLocal(evt.stageX, evt.stageY);
item.offset.x = pt.x - item.x;
item.offset.y = pt.y - item.y;
item.drag = true;
}
// mouse up event
function onMouseUp(evt){
var item = evt.currentTarget;
item.drag = false;
var pt = item.localToLocal(item.dot.x, item.dot.y, item.state1.hitBox);
if(item.stateplace1.hitBox.hitTest(pt.x, pt.y) ){
item.x = item.stateplace1.x;
item.y = item.stateplace1.y;
item.mouseEnabled = false; // prevents object from being move when place correctly
}else{
item.x = item.originX;
item.y = item.originY;
}
}
function onMouseMove(evt){
var item = evt.currentTarget;
if (item.drag){
var pt = item.parent.globalToLocal(evt.stageX, evt.stageY);
item.x = pt.x - item.offset.x;
item.y = pt.y - item.offset.y;
}
// mouse move event
}
I'm building a little module in javascript to act like a pack of cards. My first method works but was quite simple, and so i wanted to create some shuffle methods that mimic the idea behind real world card shuffling.
Amongst some other useful functions I've create riffle, overhand and cut functions, that all seem to do there job, but when calling them repeatedly in sequence the returned pack amount is inconsistent, from running it over and over again it appears to be some sort of race condition, but can't seem to get my head around how to avoid it.
The relevant private methods are :
riffle : function riffle() {
var top = Pack.slice(0, 26);
var bottom = Pack.slice(26, 52);
Pack = [];
console.log('top is '+top.length+" and bottom is "+bottom.length);
var hand = 'right';
var result = [];
var i = 52;
while (i > 0) {
var drop = Math.floor(Math.random()*3)+1;
var cards;
if (hand === 'right' ) {
if (drop >= top.length) {
cards = top;
} else {
cards = top.splice(0, drop);
}
hand = 'left';
} else {
if (drop >= bottom.length) {
cards = bottom;
} else {
cards = bottom.splice(0, drop);
}
hand = 'right';
}
result = result.concat(cards);
i -= drop;
}
Pack = result;
console.log(Pack.length+" after riffle");
return this;
},
cut : function cut(fn) {
var top = Pack.slice(0, 26);
var bottom = Pack.slice(26, 52);
Pack = [];
console.log(top);
Pack = bottom.concat(top);
console.log(Pack.length+" after cut");
if (fn && typeof(fn) === 'function') { fn(); }
return this;
}
Later on I have a privileged method called shuffle that calls them :
shuffle : function shuffle(cb) {
State.cardsOut = [];
Internal.generatePack().cut().riffle().riffle()
.riffle().riffle().riffle();
if (cb && typeof(cb) === 'function') { cb(); }
}
Note : I start with a generate function that creates an arrray of objects representing a full pack of 52 cards. The results I get when I console log the pack at different times after shuffles and cuts vary and I can't seem to figure out why.
you can see what i'km working on here
https://gist.github.com/Pushplaybang/66bc7a1fa5d84eee2236
Any help would be awesome.
The drop variable stores the number of cards you are supposed to be riffling from either the left or right hand. However, there are two instances:
if (drop >= top.length) {
cards = top;
}
and
if (drop >= bottom.length) {
cards = bottom;
}
where drop can be greater than the number of remaining cards in the half of the pack so more cards will be subtracted from i than you have actually riffled. You can fix this by:
if (drop >= top.length) {
drop = top.length;
cards = top;
top = [];
}
and
if (drop >= bottom.length) {
drop = top.length;
cards = bottom;
bottom = [];
}
(You need to empty the arrays or you may end up adding the same cards twice).
Other issues
You have magic numbers in the code (26 and 52) these could be constants defined in the class and given appropriate names (i.e. PACK_SIZE = 52) which would mean that if you create a sub-class representing a different number of cards then it would still work.
hand has two possible values which could be represented as a boolean but you assign it strings (again you could use constants LEFT_HAND = true, RIGHT_HAND = !LEFT_HAND).
Pack appears to be a global variable - I would have thought it ought to be a member of the class.
You do not need to name the functions as this is just polluting the global namespace: riffle : function riffle() { can just be an anonymous function riffle : function() {.
Performance - you create additional arrays with each iteration and the cards are moved multiple times. This could be more efficient.
Something like this:
PACK_SIZE: 52,
riffle : function() {
var index_of_cards_riffled_from_top = 0;
var index_of_cards_riffled_from_bottom = this.PACK_SIZE / 2;
var riffled_cards = [];
while ( index_of_cards_riffled_from_top < this.PACK_SIZE / 2
|| index_of_cards_riffled_from_bottom < this.PACK_SIZE ) {
var num_cards_to_riffle_top = Math.min( this.PACK_SIZE / 2 - index_of_cards_riffled_from_top, Math.floor( Math.random() * 3 ) + 1 );
var num_cards_to_riffle_bottom = Math.min( this.PACK_SIZE - index_of_cards_riffled_from_bottom, Math.floor( Math.random() * 3 ) + 1 );
while ( num_cards_to_riffle_top > 0 ) {
riffled_cards.push( this.Pack[ index_of_cards_riffled_from_top++ ] );
num_cards_to_riffle_top--;
}
while ( num_cards_to_riffle_bottom > 0 ) {
riffled_cards.push( this.Pack[ index_of_cards_riffled_from_bottom++ ] );
num_cards_to_riffle_bottom--;
}
}
this.Pack = riffled_cards;
}
while #MTO 's answer did solve my problem, I'd like to shed some light on how I've chosen to begin refactoring this function.
riffle : function riffle() {
var cutPos = Math.floor(Math.random()*rv)+( (cardCount-rv) / 2 );
var splitPack = {
left : Pack.splice(0, cutPos),
right : Pack.splice(0, Pack.length)
};
var hand = 'right',result = [], i = 52, cards;
while(i > 0) {
drop = Math.floor(Math.random()*3)+1;
if (drop >= splitPack[ hand ].length) {
drop = splitPack[ hand ].length;
}
cards = splitPack[ hand ].splice(0, drop);
hand = (hand === 'left') ? 'right' : 'left';
result = result.concat(cards);
cards = [];
i -= drop;
}
Pack = result;
console.log(Pack.length+" after riffle");
return this;
},
a few things :
the elements that seem global are not really, as this is all wrapped within a function that creates a new "deck" object, and some elements need to be private, such as the cards remaining in the pack once dealing has begin.
While booleans would work well for the hands, I wanted to boil this down somewhat and so use the strings to select obj properties.
everything MTO said about using constants is absolutely valid.
by now splicing each time, we're removing the elements from the array.
I prefer this approach as it only uses one while loop.
lastly, this type of shuffle is meant to emulate hand shuffling, and must be combined with other hand shuffling methods, ideally in a repetitive sequence, to produce something useful,
if you want something consistently random and efficient use fischer-yates algorithm.
I have a mighty strange JavaScript problem. I have made an object oriented maze generator, which works well, but only if I call "this" (or the alias "self") right before the generator.
See code below:
// Constructor for a maze
function Maze(mazeWidth, mazeHeight) {
// Always working reference to this
var self = this;
// Has the maze been generated?
var generated = false;
// Default dimensions
var width = 20;
var height = 20;
// Check if dimensions are given
if (!isNaN(mazeWidth) && mazeWidth >= 1) {
width = parseInt(mazeWidth);
}
if (!isNaN(mazeHeight) && mazeHeight >= 1) {
height = parseInt(mazeHeight);
}
// The maze itself
var maze = {};
// Populate the maze
for (var y = 0; y < height; y++) {
maze[y] = {};
for (var x = 0; x < width; x++) {
maze[y][x] = new MazeCell(x, y);
}
}
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
}
// For some mighty strange reason "self" (or "this") needs to be called here for the code below to work
self;
// Generate the maze
(function generateMaze() {
// Map directions to its reverse
var directionMap = {};
directionMap[Maze.prototype.N] = Maze.prototype.S;
directionMap[Maze.prototype.E] = Maze.prototype.W;
directionMap[Maze.prototype.S] = Maze.prototype.N;
directionMap[Maze.prototype.W] = Maze.prototype.E;
// Depth-first search to generate the maze
(function DFS(cell, entryDirection) {
// Set the cell as discovered and open the entry direction
cell._setDiscovered();
cell._open(entryDirection);
// Find the neighbour cells
var neighbours = {};
neighbours[Maze.prototype.N] = cell.getNeighbourCell(Maze.prototype.N);
neighbours[Maze.prototype.E] = cell.getNeighbourCell(Maze.prototype.E);
neighbours[Maze.prototype.S] = cell.getNeighbourCell(Maze.prototype.S);
neighbours[Maze.prototype.W] = cell.getNeighbourCell(Maze.prototype.W);
// Check the neighbour cells in random order
for (var i = 0; i < 4; i++) {
var direction = (function() {
var result;
var count = 0;
for (var direction in neighbours) {
if (Math.random() < 1/++count)
result = direction;
}
return result;
})();
var nextCell = neighbours[direction];
delete neighbours[direction];
if (nextCell == false)
continue;
if (nextCell._isDiscovered())
continue;
// Set exit opening of this cell
cell._open(direction);
// Process next cell
DFS(nextCell, directionMap[direction]);
}
})(self.getCell(Math.floor(Math.random()*width), Math.floor(Math.random()*height)), null); // This line is the problem
})();
// ......
If I don't call "self" above the generation code, this.getCell will be called, but the first parameter will be a reference to the generateMaze-function itself. The second parameter will be unset.
It also work if I change the dummy line from "self" to "this".
Just writing "self" (or "this") on an otherwise empty line doesn't really do anything, does it? Why is it needed?
You should add semicolons after assigning a value to an attribute or variable, even if the value is a function, like here:
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
}
It should look like this:
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
};
I think that might be the cause of your problem.
Simple question which seems impossible for me because I'm just staring in the code.
Basicly I have this function, I call it X amount of times and it should put all the created divs in a array called world which I've declared outside of the function.
However, if I try to use one of these values they are "undefined".
var world = [];
function newTile(x, y, size, rotX, rotY, rotZ, tranX, tranY, tranZ, color) {
var tile = document.createElement('div');
tile.className = "tile";
tile.style.width = size+"px";
tile.style.height = size+"px";
tile.style.webkitTransform =
"rotateX("+rotX+"deg)"+
"rotateY("+rotY+"deg)"+
"rotateZ("+rotZ+"deg)"+
"translateX("+tranX+"px)"+
"translateY("+tranY+"px)"+
"translateZ("+tranZ+"px)";
tile.style.transform =
"rotateX("+rotX+"deg)"+
"rotateY("+rotY+"deg)"+
"rotateZ("+rotZ+"deg)"+
"translateX("+tranX+"px)"+
"translateY("+tranY+"px)"+
"translateZ("+tranZ+"px)";
if (x == 0 && y == 0) {
color="rgba(255,255,0,0.5)";
pathStart = tile;
pathCur = tile;
}
tile.style.backgroundColor = color;
tile.data = {
x:x,
y:y,
blacklist:0
}
tile.onclick = function() {
worldOri(null,null,null, -this.data.x*128 - 64, null, -this.data.y*128 - 64);
};
if (debug) tile.textContent = x+", "+y;
document.getElementById('world').appendChild(tile);
world[x] = [];
world[x][y] = tile;
}
Lets say I do something like:
newTile(2,6,128,90,0,0,2*128,0,6*128, "rgba(255,125,0,0.5)");
This works as intended and surely creates a div, placing it "in" another div with the id "world" and SHOULD add the div to the array "world" at [2][6]. If I now try to do something with the div, for example change color:
world[2][6].style.backgroundColor = "rgba(255,0,0,0.5)";
It returns as undefined, which I assume is that the actual adding to the "world" array didn't work, please help.
world[x] = []; will assign an empty array world[x] every time you make a call to newTile, thus "removing" all existing tiles from world[x]. Only initialize it if it doesn't exist yet:
world[x] = world[x] || [];