Interdependent derived attributes in Ampersand? - javascript

I'd like to create Ampersand State representation of a vector which simultaneously holds information about it's polar and rectangular representation.
i.e. I would like the user to be able to do:
vector.angle = 90
vector.mag = 1
console.log vector.y #=> 1
-or-
vector.x = 0
vector.y = 1
console.log vector.angle #=> 90
Can anyone think of a way to do this with ampersand?

This is the old question, but someone might need this.
Right away I can think of one way to do this. You'd need to make all of your variables independent and then listen to changes to update other values. So you'd define in props of the model variables angle, mag, x, y, and then attach event listeners in initialize of your view or somewhere else to each of these variables. For example, for angle you'd do something like this:
model.on('change:angle', function(model) {
//first, calculate new x and y values. In your model you have a new angle value
if (NEW_CALCULATED_X_VALUE != model.x || NEW_CALCULATED_Y_VALUE != model.y) {
model.set({
x: NEW_CALCULATED_X_VALUE
}, {
silent: true//I'm not triggering the 'change' event yet to avoid circular dependencies.
});
model.set({
y: NEW_CALCULATED_Y_VALUE
}, {
silent: true
});
//now, once I've set all new values, I can trigger events.
model.trigger('change:x', model); //this will trigger model.on('change:x'), but since based on this change angle and mag won't change, no circular dependency will appear.
model.trigger('change:y', model);
}
})
and repeat this for each of four variables (there is room for optimisation, but you get my idea. In order to avoid circular dependencies with this example you need to make sure that once you recalculate, for example, angle by using whatever x, you get the same angle.

Related

Rotation around a point causes weird glitches in THREE.js

Every element from my scene is made of a chain of 3 Object3Ds. The order parent-to-child is cellPivot -> modifier -> setup
setup's purpose is to permanently align a loaded object by resizing / giving some padding that must always be there. It is not supposed to be changed once set
modifier's purpose is to actually perform the real transformation on the object
cellPivot's purpose is to allow me to drag modifier into a cell grid
An example why all this is needed: let's say I have a vertical door in an orthographic perspective that I wanna fit in a 1x1 space, so I give some padding on the x-axis to align the door in the center, similar to the picture below where the orange block is the door
Since I want to move this in any cell in the map, I use cellPivot's position to decide where. I can't use right away modifier since sometimes I wanna rotate the model inside the cell, which requires to modify both position and rotation (since my models are not built around (0, 0, 0), but along +X and +Z)
I have succesfully managed to rotate these doors by rotating modifier around the center of the model (which acts as a pivot). Here's the functions that does the rotation:
three.Object3D.prototype.pivot = function(pivot, f) {
pivot = lib.VecToVector3(three, pivot); // just a conversion between libs
this.position.sub(pivot);
f(this);
this.position.add(pivot);
return this;
};
three.Object3D.prototype.pivotRotate = function(pivot, axis, theta, rotational = false, abs = false) {
if(abs)
theta -= this.rotation.y; /// not good, handles only y
this.pivot(pivot, () => this.position.applyAxisAngle(axis, theta));
if(rotational)
this.rotateOnAxis(axis, theta);
return this;
};
The line that rotates the door and works:
this.o3d.userData.modifier.pivotRotate(this.o3d.userData.center, new three.Vector3(0, 1, 0), this.rot, true);
I'm now trying to do something similar with the player too. I record what keys are pressed, I calculate the normal of the vector of desired direction (if I press W and D I'll get (1, 1), if I press just W I'll get (0, 1)), after which I use the following line to detect the angle at which the user wanna move:
Math.atan2(-normal[1], normal[0]);
I have already tested that the angle is correct. On top of that, the codebase before "rotating around a pivot" used the same code and it worked fine
Everytime there's actually a direction the user wanna go, I'll run the following line:
this.o3d.userData.modifier.pivotRotate(this.o3d.userData.center, new three.Vector3(0, 1, 0), Math.atan2(-normal[1], normal[0]), true, true);
If the user just keeps a key pressed, then abs will make sure that no visible rotation is made (since theta will be 0)
Here's the problem: everytime I press A, be it in combination with W or S or not, the character will rotate like insane. I put after the line from above the following code to see what's happening:
com.log(new three.Euler().setFromQuaternion(this.o3d.userData.setup.getWorldQuaternion(new three.Quaternion())));
I'm getting this:
As you can see, x and z are reaching -pi, and y bouces back and forth. This does not happen for any other combination that does not contain key A
Update after 2 days:
I have rewrote my function like this:
I got these in console while trying to move in the problematic positions:
As it can be seen in the first log, my target is at rotation 0 and is going for -2.35..., but rotAfterRot is showing weird results..: -pi and -.78...
This is the result of running this.rotateOnAxis(axis, theta). I have changed this exact line with this.rotation.y += theta. Now everything is working as it should be: no weird -pi and rotAfterRot.y is actually theta
My guess is that rotateOnAxis is also counting other features of the object, like position, but still can't figure how it spits that -pi

d3 setting multiple attributes from the same function?

Is there any way to set multiple attributes from the same function?
d3.selectAll('.myshape')
.attr('y',function(d,i) { ... calculates something ... })
.attr('height',function(d,i) { ... calculates something very similar... })
I would like to calculate y1 and y2 at the same time and then set y = y1 and height = y2-y1. But the standard way of doing this in d3 seems to be having separate functions per attribute. Is there a better way?
If I understand your question correctly, you have an intensive calculation that you would like to compute only once per element, regardless the number of attributes you're setting.
That being the case, you can use an each to pass the current element (this), along with the datum, the index and the group, if you need them:
d3.selectAll(".myShape").each(function(d,i,n){
//Here you put the complex calculation
//which will assign the values of y1 and y2.
//This calculation runs only once per element
d3.select(this).attr("y", y1).attr("height", y2 - y1)
});
Not exactly. But you could solve the issue in a indirect way. This adds slightly more overhead but worth it imo. JS doesn't have nice "compile-time" (as far as the js engine is concerned) macros like C family, that give you nice expansions without runtime cost.
like this (you may already know):
let myshape = d3.selectAll('.myshape')
['y', 'height', ...].forEach(attr => myshape.attr(attr, calcAttr));
function calcAttr(a, i) {
// shared code
switch (a) {
case 'y':
//attr specific
break;
}
}

Is possible to have an event linked to object's own parameter?

I have an object (lets say a circle) with specific parameters (x,y and radius).
var circle = new Kinetic.Circle({
x: 100,
y: 100,
radius: 20
}
The radius of the circle is thereafter dynamically changed by the user.
Now I want a listener that continuously observes the radius, and every time the radius goes below 5, it deletes the circle. Something like:
circle.on("radiusLessThanFive",function (e) {
this.remove();
}
So how do I configure this radiusLessThanFive event that constantly listens to the radius of the circle?
Well, unfortunately, there isn't something built in that would do that. So you just have to make it part of your application logic.
Let's say you have a layer called shapesLayer. You can do:
function radiusLessThanFive(){
var circles = shapesLayer.get('.circle'); // I can't remember the getting all circles syntax, but something like this
for ( var i=0; i<circles.length; i++) {
if(cicrles[i].getRadius() < 5 ){
circles[i].remove();
shapesLayer.draw();
}
}
}
While this is not the most efficient solution ever, it gets the job done... unless you know how to create your own custom events in javascript (which would have to do the same thing as the above function anyways).
In case you'd like to take the custom event road, here is a link: http://www.sitepoint.com/javascript-custom-events/
Edit: regarding comment below
function radiusLessThanFive(myCircle){ //function which checks radius of single circle and deletes if less than 5
if(myCircle.getRadius() < 5 ){
myCircle.remove();
myCircle.getParent().draw();
}
}
}

Changing an Object's attributes also Affects Another Object

Problem: When I change the points of a polygon poly2, it also changes the points of another polygon poly!!
Why does changing one also change the other, and how do we decouple them?
console.log(poly.getPoints()[1].x); // 100
// Make a change to `poly2`
poly2.setPoints(poly.getPoints());
poly2.getPoints()[1].x=200
console.log(poly.getPoints()[1].x); // 200 (both poly and poly2 are affected!)
jsfiddle: http://jsfiddle.net/8hFyv/
poly2.setPoints(poly.getPoints());
This is your problem. The points array is the very same object.
Since you have arrays in your array, the slice(0) trick won't work, you need deep copy.
Fortunately, you're using jQuery, which has a method to do it.
Replace the above line with:
poly2.setPoints($.extend(true, [], poly.getPoints()));
Your poly and poly2 object are referencing the same array of points when you do this:
poly2.setPoints(poly.getPoints());
Change it to this:
poly2.setPoints([0, 0, 100, 0, 100, 100, 0, 100]);
To clone the points, vs. sharing them between polygons, you'll need to create new objects for each yourself.
You can do this with map:
poly2.setPoints(poly.getPoints().map(function (p) {
return { x: p.x, y: p.y };
}));
Or, with jQuery.map:
poly2.setPoints($.map(poly.getPoints(), function (p) {
return { x: p.x, y: p.y };
}));
The other answers are correct in assessing the problem, but there's another way you can solve it: "clone" the points array when you set it. In other words:
poly2.setPoints(poly.getPoints().slice());
If for some reason getPoints() returns something other than an array, you'll need a different cloning approach (eg. the one that axel.michel suggested), but since I think it does that should work for you.
The problem is, that poly.getPoints is a set of Kinetic Pointer Objects, to get rid of it, try the following:
poly2.setPoints(JSON.parse(JSON.stringify(poly.getPoints())));

How do I implement something like pointers in javascript?

I know that javascript doesn't have pointers in terms of a variable referring to a place in memory but what I have is a number of variables which are subject to change and dependent on each other.
For example:
Center (x,y) = (offsetLeft + width/scale , offsetTop + height/scale)
As of now I have rewritten the equation in terms of each individual variable and after any changes I call the appropriate update function.
For example:
If scale changes, then then the center, height, and width stay the same. So I call
updateoffset() {
offsetLeft = centerx - width/scale;
offsetTop = centery - height/scale;
}
Is this the easiest way to update each of these variables when any of them changes?
I read your question in two different ways, so two answers:
Updating calculated values when other values change
The two usual ways are: 1. To require that the values only be changed via "setter" functions, and then you use that as an opportunity to recalcuate the things that changed, or 2. To require that you use "getter" functions to get the calculated value, which you calculate on the fly (or if that's expensive, you retrieve from a cached calculation).
Returning multiple values from a function
If you're looking for a way of returning multiple values from a single function, you can do that easily by returning an object. Example:
// Definition:
function center(offsetLeft, offsetTop, width, height, scale) {
return {
x: offsetLeft + width/scale,
y: offsetTop + height/scale
};
}
// Use:
var pos = center(100, 120, 10, 20, 2);
// pos.x is now 105
// pos.y is now 130

Categories