My question probably has an easy answer but I can't seem to find a solution for my problem.
I have some code to do a put request to the phillips hue api and it requires:
{"xy":[0.300,0.300]} but JSON.stringify() returns {"xy":["0.300","0.300"]}
how can I get it to return the correct values?
Here is my code:
function AllRed() {
var url = 'http://192.168.168.124/api/husm18zj4JGeeHxoUKwDrCVfKRw6nicB25dLnYHX/groups/*/action';
var r = 255;
var g = 0;
var b = 0;
var model = 'LCT001';
var data = JSON.stringify({'xy': RGBtoXY(r, g, b, model)});//RGBtoXY(r, g, b, model)
$.put(url, data);
}
And here is the code for RGBtoXY which I got from another thread here on stackoverflow, unfortunately I can't find the specific thread anymore.
function XYPoint(x, y)
{
if (this instanceof XYPoint)
{
this.x = x;
this.y = y;
} else
{
return new XYPoint(x, y);
}
}
/**
* Get Color points according to light model
* #param model string Ex: LLC010
* #returns {Array}
*/
function colorPointsForModel(model)
{
var colorPoints = [];
if (model === 'LCT001')
{
colorPoints.push(XYPoint(0.500, 0.322));
colorPoints.push(XYPoint(0.4091, 0.518));
colorPoints.push(XYPoint(0.167, 0.04));
} else if (model === 'LLC006' || model === 'LLC007')
{
colorPoints.push(XYPoint(0.704, 0.296));
colorPoints.push(XYPoint(0.2151, 0.7106));
colorPoints.push(XYPoint(0.138, 0.08));
} else
{
// Default construct triangle wich contains all values
colorPoints.push(XYPoint(1.0, 0.0));
colorPoints.push(XYPoint(0.0, 1.0));
colorPoints.push(XYPoint(0.0, 0.0));
}
return colorPoints;
}
/**
* Method to see if the given XY value is within the reach of the lamps.
*
* #param p the point containing the X,Y value
* #param colorPoints color points array containing RGB XYPoints
* #return true if within reach, false otherwise.
*/
function checkPointInLampsReach(p, colorPoints)
{
var red = colorPoints[0];
var green = colorPoints[1];
var blue = colorPoints[2];
var v1 = XYPoint(green.x - red.x, green.y - red.y);
var v2 = XYPoint(blue.x - red.x, blue.y - red.y);
var q = XYPoint(p.x - red.x, p.y - red.y);
var s = crossProduct(q, v2) / crossProduct(v1, v2);
var t = crossProduct(v1, q) / crossProduct(v1, v2);
return ((s >= 0.0) && (t >= 0.0) && (s + t <= 1.0));
}
/**
* Is Not a number?
* Note: NaN is the only JavaScript value that is treated as unequal to itself
* #param val
* #returns {boolean}
*/
function isNaN(val)
{
return val !== val;
}
/**
* Calculates crossProduct of two 2D vectors / points.
*
* #param p1 first point used as vector
* #param p2 second point used as vector
* #return crossProduct of vectors
*/
function crossProduct(p1, p2)
{
return (p1.x * p2.y - p1.y * p2.x);
}
/**
* Converts RGB to XY and Brightness
* #param r integer 0-255
* #param g integer 0-255
* #param b integer 0-255
* #param model string
*/
function RGBtoXY(red, green, blue, model)
{
if (red > 1 || green > 1 || blue > 1)
{
red /= 255;
green /= 255;
blue /= 255;
}
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
var X = red * 0.649926 + green * 0.103455 + blue * 0.197109;
var Y = red * 0.234327 + green * 0.743075 + blue * 0.022598;
var Z = red * 0.0000000 + green * 0.053077 + blue * 1.035763;
var cx = X / (X + Y + Z);
var cy = Y / (X + Y + Z);
if (isNaN(cx)) {
cx = 0.0;
}
if (isNaN(cy)) {
cy = 0.0;
}
//Check if the given XY value is within the colourreach of our lamps.
var xyPoint = XYPoint(cx, cy);
var colorPoints = colorPointsForModel(model);
var inReachOfLamps = checkPointInLampsReach(xyPoint, colorPoints);
if (!inReachOfLamps)
{
//It seems the colour is out of reach
//let's find the closest colour we can produce with our lamp and send this XY value out.
//Find the closest point on each line in the triangle.
var pAB = getClosestPointToPoints(colorPoints[cptRED], colorPoints[cptGREEN], xyPoint);
var pAC = getClosestPointToPoints(colorPoints[cptBLUE], colorPoints[cptRED], xyPoint);
var pBC = getClosestPointToPoints(colorPoints[cptGREEN], colorPoints[cptBLUE], xyPoint);
//Get the distances per point and see which point is closer to our Point.
var dAB = getDistanceBetweenTwoPoints(xyPoint, pAB);
var dAC = getDistanceBetweenTwoPoints(xyPoint, pAC);
var dBC = getDistanceBetweenTwoPoints(xyPoint, pBC);
var lowest = dAB;
var closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
lowest = dBC;
closestPoint = pBC;
}
//Change the xy value to a value which is within the reach of the lamp.
cx = closestPoint.x;
cy = closestPoint.y;
}
retval = [cx.toPrecision(3), cy.toPrecision(3)];
return retval;
}
/**
* Find the closest point on a line.
* This point will be within reach of the lamp.
*
* #param A the point where the line starts
* #param B the point where the line ends
* #param P the point which is close to a line.
* #return the point which is on the line.
*/
function getClosestPointToPoints(A, B, P)
{
var AP = XYPoint(P.x - A.x, P.y - A.y);
var AB = XYPoint(B.x - A.x, B.y - A.y);
var ab2 = AB.x * AB.x + AB.y * AB.y;
var ap_ab = AP.x * AB.x + AP.y * AB.y;
var t = ap_ab / ab2;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
return XYPoint(A.x + AB.x * t, A.y + AB.y * t);
}
/**
* Find the distance between two points.
*
* #param one
* #param two
* #return the distance between point one and two
*/
// + (float)getDistanceBetweenTwoPoints:(CGPoint)one point2:(CGPoint)two {
function getDistanceBetweenTwoPoints(one, two)
{
var dx = one.x - two.x; // horizontal difference
var dy = one.y - two.y; // vertical difference
return Math.sqrt(dx * dx + dy * dy);
}
function XYtoRGB(x, y, brightness, model)
{
var xy = XYPoint(x, y);
var colorPoints = colorPointsForModel(model);
var inReachOfLamps = checkPointInLampsReach(xy, colorPoints);
console.log('inReachOfLamps', inReachOfLamps);
if (!inReachOfLamps) {
//It seems the colour is out of reach
//let's find the closest colour we can produce with our lamp and send this XY value out.
//Find the closest point on each line in the triangle.
var pAB = getClosestPointToPoints(colorPoints[cptRED], colorPoints[cptGREEN], xy);
var pAC = getClosestPointToPoints(colorPoints[cptBLUE], colorPoints[cptRED], xy);
var pBC = getClosestPointToPoints(colorPoints[cptGREEN], colorPoints[cptBLUE], xy);
//Get the distances per point and see which point is closer to our Point.
var dAB = getDistanceBetweenTwoPoints(xy, pAB);
var dAC = getDistanceBetweenTwoPoints(xy, pAC);
var dBC = getDistanceBetweenTwoPoints(xy, pBC);
var lowest = dAB;
var closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
lowest = dBC;
closestPoint = pBC;
}
//Change the xy value to a value which is within the reach of the lamp.
xy.x = closestPoint.x;
xy.y = closestPoint.y;
}
var x = xy.x;
var y = xy.y;
var z = 1.0 - x - y;
var Y = brightness;
var X = (Y / y) * x;
var Z = (Y / y) * z;
var r = X * 3.2410 - Y * 1.5374 - Z * 0.4986;
var g = -X * 0.9692 + Y * 1.8760 + Z * 0.0416;
var b = X * 0.0556 - Y * 0.2040 + Z * 1.0570;
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
if (r < 0)
{
r = 0;
}
if (g < 0)
{
g = 0;
}
if (b < 0)
{
b = 0;
}
if (r > 1 || g > 1 || b > 1)
{
var max = Math.max(r, g, b);
r /= max;
g /= max;
b /= max;
}
r *= 255;
g *= 255;
b *= 255;
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
return {
r: r,
g: g,
b: b
};
}
Your "problem" is this line
retval = [cx.toPrecision(3), cy.toPrecision(3)];
As already noted in comments toPrecision() returns a string, which when serialized will result in the ".
So either drop the toPrecision() calls here, or convert to a number again later:
var xy = RGBtoXY(r, g, b, model);
// convert to numbers again
xy = xy.map( Number );
var data = JSON.stringify({'xy': xy });//RGBtoXY(r, g, b, model)
I've got the linear component of collision resolution down relatively well, but I can't quite figure out how to do the same for the angular one. From what I've read, it's something like... torque = point of collision x linear velocity. (cross product) I tried to incorporate an example I found into my code but I actually don't see any rotation at all when objects collide. The other fiddle works perfectly with a rudimentary implementation of the seperating axis theorem and the angular velocity calculations. Here's what I've come up with...
Property definitions (orientation, angular velocity, and angular acceleration):
rotation: 0,
angularVelocity: 0,
angularAcceleration: 0
Calculating the angular velocity in the collision response:
var pivotA = this.vector(bodyA.x, bodyA.y);
bodyA.angularVelocity = 1 * 0.2 * (bodyA.angularVelocity / Math.abs(bodyA.angularVelocity)) * pivotA.subtract(isCircle ? pivotA.add(bodyA.radius) : {
x: pivotA.x + boundsA.width,
y: pivotA.y + boundsA.height
}).vCross(bodyA.velocity);
var pivotB = this.vector(bodyB.x, bodyB.y);
bodyB.angularVelocity = 1 * 0.2 * (bodyB.angularVelocity / Math.abs(bodyB.angularVelocity)) * pivotB.subtract(isCircle ? pivotB.add(bodyB.radius) : {
x: pivotB.x + boundsB.width,
y: pivotB.y + boundsB.height
}).vCross(bodyB.velocity);
Updating the orientation in the update loop:
var torque = 0;
torque += core.objects[o].angularVelocity * -1;
core.objects[o].angularAcceleration = torque / core.objects[o].momentOfInertia();
core.objects[o].angularVelocity += core.objects[o].angularAcceleration;
core.objects[o].rotation += core.objects[o].angularVelocity;
I would post the code that I have for calculating the moments of inertia but there's a seperate one for every object so that would be a bit... lengthy. Nonetheless, here's the one for a circle as an example:
return this.mass * this.radius * this.radius / 2;
Just to show the result, here's my fiddle. As shown, objects do not rotate on collision. (not exactly visible with the circles, but it should work for the zero and seven)
What am I doing wrong?
EDIT: Reason they weren't rotating at all was because of an error with groups in the response function -- it rotates now, just not correctly. However, I've commented that out for now as it messes things up.
Also, I've tried another method for rotation. Here's the code in the response:
_bodyA.angularVelocity = direction.vCross(_bodyA.velocity) / (isCircle ? _bodyA.radius : boundsA.width);
_bodyB.angularVelocity = direction.vCross(_bodyB.velocity) / (isCircle ? _bodyB.radius : boundsB.width);
Note that direction refers to the "collision normal".
Angular and linear acceleration due to force vector
Angular and directional accelerations due to an applied force are two components of the same thing and can not be separated. To get one you need to solve for both.
Define the calculations
From simple physics and standing on shoulders we know the following.
F is force (equivalent to inertia)
Fv is linear force
Fa is angular force
a is acceleration could be linear or rotational depending on where it is used
v is velocity. For angular situations it is the tangential component only
m is mass
r is radius
For linear forces
F = m * v
From which we derive
m = F / v
v = F / m
For rotational force (v is tangential velocity)
F = r * r * m * (v / r) and simplify F = r * m * v
From which we derive
m = F / ( r * v )
v = F / ( r * m )
r = F / ( v * m )
Because the forces we apply are instantaneous we can interchange a acceleration and v velocity to give all the following formulas
Linear
F = m * a
m = F / a
a = F / m
Rotational
F = r * m * a
m = F / ( r * a )
a = F / ( r * m )
r = F / ( a * m )
As we are only interested in the change in velocity for both linear and rotation solutions
a1 = F / m
a2 = F / ( r * m )
Where a1 is acceleration in pixels per frame2 and a2 is acceleration in radians per frame2 ( the frame squared just denotes it is acceleration)
From 1D to 2D
Because this is a 2D solution and all above are 1D we need to use vectors. I for this problem use two forms of the 2D vector. Polar that has a magnitude (length, distance, the like...) and direction. Cartesian which has x and y. What a vector represents depends on how it is used.
The following functions are used as helpers in the solution. They are written in ES6 so for non compliant browsers you will have to adapt them, though I would not ever suggest you use these as they are written for convenience, they are very inefficient and do a lot of redundant calculations.
Converts a vector from polar to cartesian returning a new one
function polarToCart(pVec, retV = {x : 0, y : 0}) {
retV.x = Math.cos(pVec.dir) * pVec.mag;
retV.y = Math.sin(pVec.dir) * pVec.mag;
return retV;
}
Converts a vector from cartesian to polar returning a new one
function cartToPolar(vec, retV = {dir : 0, mag : 0}) {
retV.dir = Math.atan2(vec.y, vec.x);
retV.mag = Math.hypot(vec.x, vec.y);
return retV;
}
Creates a polar vector
function polar(mag = 1, dir = 0) {
return validatePolar({dir : dir,mag : mag});
}
Create a vector as a cartesian
function vector(x = 1, y = 0) {
return {x : x, y : y};
}
True is the arg vec is a vector in polar form
function isPolar(vec) {
if (vec.mag !== undefined && vec.dir !== undefined) {return true;}
return false;
}
Returns true if arg vec is a vector in cartesian form
function isCart(vec) {
if (vec.x !== undefined && vec.y !== undefined) {return true;}
return false;
}
Returns a new vector in polar form also ensures that vec.mag is positive
function asPolar(vec){
if(isCart(vec)){ return cartToPolar(vec); }
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
return { dir : vec.dir, mag : vec.mag };
}
Copy and converts an unknown vec to cart if not already
function asCart(vec){
if(isPolar(vec)){ return polarToCart(vec); }
return { x : vec.x, y : vec.y};
}
Calculations can result in a negative magnitude though this is valid for some calculations this results in the incorrect vector (reversed) this simply validates that the polar vector has a positive magnitude it does not change the vector just the sign and direction
function validatePolar(vec) {
if (isPolar(vec)) {
if (vec.mag < 0) {
vec.mag = - vec.mag;
vec.dir += PI;
}
}
return vec;
}
The Box
Now we can define an object that we can use to play with. A simple box that has position, size, mass, orientation, velocity and rotation
function createBox(x,y,w,h){
var box = {
x : x, // pos
y : y,
r : 0.1, // its rotation AKA orientation or direction in radians
h : h, // its height
w : w, // its width
dx : 0, // delta x in pixels per frame 1/60th second
dy : 0, // delta y
dr : 0.0, // deltat rotation in radians per frame 1/60th second
mass : w * h, // mass in things
update :function(){
this.x += this.dx;
this.y += this.dy;
this.r += this.dr;
},
}
return box;
}
Applying a force to an object
So now we can redefine some terms
F (force) is a vector force the magnitude is the force and it has a direction
var force = polar(100,0); // create a force 100 units to the right (0 radians)
The force is meaningless without a position where it is applied.
Position is a vector that just holds and x and y location
var location = vector(canvas.width/2, canvas.height/2); // defines a point in the middle of the canvas
Directional vector holds the direction and distance between to positional vectors
var l1 = vector(canvas.width/2, canvas.height/2); // defines a point in the middle of the canvas
var l2 = vector(100,100);
var direction = asPolar(vector(l2.x - l1.x, l2.y - l1.y)); // get the direction as polar vector
direction now has the direction from canvas center to point (100,100) and the distance.
The last thing we need to do is extract the components from a force vector along a directional vector. When you apply a force to an object the force is split into two, one is the force along the line to the object center and adds to the object acceleration, the other force is at 90deg to the line to the object center (the tangent) and that is the force that changes rotation.
To get the two components you get the difference in direction between the force vector and the directional vector from where the force is applied to the object center.
var force = polar(100,0); // the force
var forceLoc = vector(50,50); // the location the force is applied
var direction2Center = asPolar(vector(box.x - forceLoc.x, box.y - forceLoc.y)); // get the direction as polar vector
var pheta = direction2Center - force.dir; // get the angle between the force and object center
Now that you have that angle pheta the force can be split into its rotational and linear components with trig.
var F = force.mag; // get the force magnitude
var Fv = Math.cos(pheta) * F; // get the linear force
var Fa = Math.sin(pheta) * F; // get the angular force
Now the forces can be converted back to accelerations for linear a = F/m and angular a = F/(m*r)
accelV = Fv / box.mass; // linear acceleration in pixels
accelA = Fa / (box.mass * direction2Center.mag); // angular acceleration in radians
You then convert the linear force back to a vector that has a direction to the center of the object
var forceV = polar(Fv, direction2Center);
Convert is back to the cartesian so we can add it to the object deltaX and deltaY
forceV = asCart(forceV);
And add the acceleration to the box
box.dx += forceV.x;
box.dy += forceV.y;
Rotational acceleration is just one dimensional so just add it to the delta rotation of the box
box.dr += accelA;
And that is it.
Function to apply force to Box
The function if attached to the box will apply a force vector at a location to the box.
Attach to the box like so
box.applyForce = applyForce; // bind function to the box;
You can then call the function via the box
box.applyForce(force, locationOfForce);
function applyForce(force, loc){ // force is a vector, loc is a coordinate
var toCenter = asPolar(vector(this.x - loc.x, this.y - loc.y)); // get the vector to the center
var pheta = toCenter.dir - force.dir; // get the angle between the force and the line to center
var Fv = Math.cos(pheta) * force.mag; // Split the force into the velocity force along the line to the center
var Fa = Math.sin(pheta) * force.mag; // and the angular force at the tangent to the line to the center
var accel = asPolar(toCenter); // copy the direction to center
accel.mag = Fv / this.mass; // now use F = m * a in the form a = F/m to get acceleration
var deltaV = asCart(accel); // convert acceleration to cartesian
this.dx += deltaV.x // update the box delta V
this.dy += deltaV.y //
var accelA = Fa / (toCenter.mag * this.mass); // for the angular component get the rotation
// acceleration from F=m*a*r in the
// form a = F/(m*r)
this.dr += accelA;// now add that to the box delta r
}
The Demo
The demo is only about the function applyForce the stuff to do with gravity and bouncing are only very bad approximations and should not be used for any physic type of stuff as they do not conserve energy.
Click and drag to apply a force to the object in the direction that the mouse is moved.
const PI90 = Math.PI / 2;
const PI = Math.PI;
const PI2 = Math.PI * 2;
const INSET = 10; // playfeild inset
const ARROW_SIZE = 6
const SCALE_VEC = 10;
const SCALE_FORCE = 0.15;
const LINE_W = 2;
const LIFE = 12;
const FONT_SIZE = 20;
const FONT = "Arial Black";
const WALL_NORMS = [PI90,PI,-PI90,0]; // dirction of the wall normals
var box = createBox(200, 200, 50, 100);
box.applyForce = applyForce; // Add this function to the box
// render / update function
var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var i;
var mouse = {
x : 0, y : 0,buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element = document){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
mouse.element = undefined;
}
}
return mouse;
})();
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if(box){
box.w = canvas.width * 0.10;
box.h = box.w * 2;
box.mass = box.w * box.h;
}
}
window.addEventListener("resize",resizeCanvas);
resizeCanvas();
mouse.start(canvas)
var tempVecs = [];
function addTempVec(v,vec,col,life = LIFE,scale = SCALE_VEC){tempVecs.push({v:v,vec:vec,col:col,scale:scale,life:life,sLife:life});}
function drawTempVecs(){
for(var i = 0; i < tempVecs.length; i ++ ){
var t = tempVecs[i]; t.life -= 1;
if(t.life <= 0){tempVecs.splice(i, 1); i--; continue}
ctx.globalAlpha = (t.life / t.sLife)*0.25;
drawVec(t.v, t.vec ,t.col, t.scale)
}
}
function drawVec(v,vec,col,scale = SCALE_VEC){
vec = asPolar(vec)
ctx.setTransform(1,0,0,1,v.x,v.y);
var d = vec.dir;
var m = vec.mag;
ctx.rotate(d);
ctx.beginPath();
ctx.lineWidth = LINE_W;
ctx.strokeStyle = col;
ctx.moveTo(0,0);
ctx.lineTo(m * scale,0);
ctx.moveTo(m * scale-ARROW_SIZE,-ARROW_SIZE);
ctx.lineTo(m * scale,0);
ctx.lineTo(m * scale-ARROW_SIZE,ARROW_SIZE);
ctx.stroke();
}
function drawText(text,x,y,font,size,col){
ctx.font = size + "px "+font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.setTransform(1,0,0,1,x,y);
ctx.globalAlpha = 1;
ctx.fillStyle = col;
ctx.fillText(text,0,0);
}
function createBox(x,y,w,h){
var box = {
x : x, // pos
y : y,
r : 0.1, // its rotation AKA orientation or direction in radians
h : h, // its height, and I will assume that its depth is always equal to its height
w : w, // its width
dx : 0, // delta x in pixels per frame 1/60th second
dy : 0, // delta y
dr : 0.0, // deltat rotation in radians per frame 1/60th second
getDesc : function(){
var vel = Math.hypot(this.dx ,this.dy);
var radius = Math.hypot(this.w,this.h)/2
var rVel = Math.abs(this.dr * radius);
var str = "V " + (vel*60).toFixed(0) + "pps ";
str += Math.abs(this.dr * 60 * 60).toFixed(0) + "rpm ";
str += "Va " + (rVel*60).toFixed(0) + "pps ";
return str;
},
mass : function(){ return (this.w * this.h * this.h)/1000; }, // mass in K things
draw : function(){
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,this.x,this.y);
ctx.rotate(this.r);
ctx.fillStyle = "#444";
ctx.fillRect(-this.w/2, -this.h/2, this.w, this.h)
ctx.strokeRect(-this.w/2, -this.h/2, this.w, this.h)
},
update :function(){
this.x += this.dx;
this.y += this.dy;
this.dy += 0.061; // alittle gravity
this.r += this.dr;
},
getPoint : function(which){
var dx,dy,x,y,xx,yy,velocityA,velocityT,velocity;
dx = Math.cos(this.r);
dy = Math.sin(this.r);
switch(which){
case 0:
x = -this.w /2;
y = -this.h /2;
break;
case 1:
x = this.w /2;
y = -this.h /2;
break;
case 2:
x = this.w /2;
y = this.h /2;
break;
case 3:
x = -this.w /2;
y = this.h /2;
break;
case 4:
x = this.x;
y = this.y;
}
var xx,yy;
xx = x * dx + y * -dy;
yy = x * dy + y * dx;
var details = asPolar(vector(xx, yy))
xx += this.x;
yy += this.y;
velocityA = polar(details.mag * this.dr, details.dir + PI90);
velocityT = vectorAdd(velocity = vector(this.dx, this.dy), velocityA);
return {
velocity : velocity, // only directional
velocityT : velocityT, // total
velocityA : velocityA, // angular only
pos : vector(xx, yy),
radius : details.mag,
}
},
}
box.mass = box.mass(); // Mass remains the same so just set it with its function
return box;
}
// calculations can result in a negative magnitude though this is valide for some
// calculations this results in the incorrect vector (reversed)
// this simply validates that the polat vector has a positive magnitude
// it does not change the vector just the sign and direction
function validatePolar(vec){
if(isPolar(vec)){
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
}
return vec;
}
// converts a vector from polar to cartesian returning a new one
function polarToCart(pVec, retV = {x : 0, y : 0}){
retV.x = Math.cos(pVec.dir) * pVec.mag;
retV.y = Math.sin(pVec.dir) * pVec.mag;
return retV;
}
// converts a vector from cartesian to polar returning a new one
function cartToPolar(vec, retV = {dir : 0, mag : 0}){
retV.dir = Math.atan2(vec.y,vec.x);
retV.mag = Math.hypot(vec.x,vec.y);
return retV;
}
function polar (mag = 1, dir = 0) { return validatePolar({dir : dir, mag : mag}); } // create a polar vector
function vector (x= 1, y= 0) { return {x: x, y: y}; } // create a cartesian vector
function isPolar (vec) { if(vec.mag !== undefined && vec.dir !== undefined) { return true; } return false; }// returns true if polar
function isCart (vec) { if(vec.x !== undefined && vec.y !== undefined) { return true; } return false; }// returns true if cartesian
// copy and converts an unknown vec to polar if not already
function asPolar(vec){
if(isCart(vec)){ return cartToPolar(vec); }
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
return { dir : vec.dir, mag : vec.mag };
}
// copy and converts an unknown vec to cart if not already
function asCart(vec){
if(isPolar(vec)){ return polarToCart(vec); }
return { x : vec.x, y : vec.y};
}
// normalise makes a vector a unit length and returns it as a cartesian
function normalise(vec){
var vp = asPolar(vec);
vap.mag = 1;
return asCart(vp);
}
function vectorAdd(vec1, vec2){
var v1 = asCart(vec1);
var v2 = asCart(vec2);
return vector(v1.x + v2.x, v1.y + v2.y);
}
// This splits the vector (polar or cartesian) into the components along dir and the tangent to that dir
function vectorComponentsForDir(vec,dir){
var v = asPolar(vec); // as polar
var pheta = v.dir - dir;
var Fv = Math.cos(pheta) * v.mag;
var Fa = Math.sin(pheta) * v.mag;
var d1 = dir;
var d2 = dir + PI90;
if(Fv < 0){
d1 += PI;
Fv = -Fv;
}
if(Fa < 0){
d2 += PI;
Fa = -Fa;
}
return {
along : polar(Fv,d1),
tangent : polar(Fa,d2)
};
}
function doCollision(pointDetails, wallIndex){
var vv = asPolar(pointDetails.velocity); // Cartesian V make sure the velocity is in cartesian form
var va = asPolar(pointDetails.velocityA); // Angular V make sure the velocity is in cartesian form
var vvc = vectorComponentsForDir(vv, WALL_NORMS[wallIndex])
var vac = vectorComponentsForDir(va, WALL_NORMS[wallIndex])
vvc.along.mag *= 1.18; // Elastic collision requiers that the two equal forces from the wall
vac.along.mag *= 1.18; // against the box and the box against the wall be summed.
// As the wall can not move the result is that the force is twice
// the force the box applies to the wall (Yes and currently force is in
// velocity form untill the next line)
vvc.along.mag *= box.mass; // convert to force
//vac.along.mag/= pointDetails.radius
vac.along.mag *= box.mass
vvc.along.dir += PI; // force is in the oppisite direction so turn it 180
vac.along.dir += PI; // force is in the oppisite direction so turn it 180
// split the force into components based on the wall normal. One along the norm the
// other along the wall
vvc.tangent.mag *= 0.18; // add friction along the wall
vac.tangent.mag *= 0.18;
vvc.tangent.mag *= box.mass //
vac.tangent.mag *= box.mass
vvc.tangent.dir += PI; // force is in the oppisite direction so turn it 180
vac.tangent.dir += PI; // force is in the oppisite direction so turn it 180
// apply the force out from the wall
box.applyForce(vvc.along, pointDetails.pos)
// apply the force along the wall
box.applyForce(vvc.tangent, pointDetails.pos)
// apply the force out from the wall
box.applyForce(vac.along, pointDetails.pos)
// apply the force along the wall
box.applyForce(vac.tangent, pointDetails.pos)
//addTempVec(pointDetails.pos, vvc.tangent, "red", LIFE, 10)
//addTempVec(pointDetails.pos, vac.tangent, "red", LIFE, 10)
}
function applyForce(force, loc){ // force is a vector, loc is a coordinate
validatePolar(force); // make sure the force is a valid polar
// addTempVec(loc, force,"White", LIFE, SCALE_FORCE) // show the force
var l = asCart(loc); // make sure the location is in cartesian form
var toCenter = asPolar(vector(this.x - l.x, this.y - l.y));
var pheta = toCenter.dir - force.dir;
var Fv = Math.cos(pheta) * force.mag;
var Fa = Math.sin(pheta) * force.mag;
var accel = asPolar(toCenter); // copy the direction to center
accel.mag = Fv / this.mass; // now use F = m * a in the form a = F/m
var deltaV = asCart(accel); // convert it to cartesian
this.dx += deltaV.x // update the box delta V
this.dy += deltaV.y
var accelA = Fa / (toCenter.mag * this.mass); // for the angular component get the rotation
// acceleration
this.dr += accelA;// now add that to the box delta r
}
// make a box
ctx.globalAlpha = 1;
var lx,ly;
function update(){
// clearLog();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "#888";
ctx.fillRect(INSET, INSET, canvas.width - INSET * 2, canvas.height - INSET * 2);
ctx.strokeRect(INSET, INSET, canvas.width - INSET * 2, canvas.height - INSET * 2);
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
box.update();
box.draw();
if(mouse.buttonRaw & 1){
var force = asPolar(vector(mouse.x - lx, mouse.y - ly));
force.mag *= box.mass * 0.1;
box.applyForce(force,vector(mouse.x, mouse.y))
addTempVec(vector(mouse.x, mouse.y), asPolar(vector(mouse.x - lx, mouse.y - ly)), "Cyan", LIFE, 5);
}
lx = mouse.x;
ly = mouse.y;
for(i = 0; i < 4; i++){
var p = box.getPoint(i);
// only do one collision per frame or we will end up adding energy
if(p.pos.x < INSET){
box.x += (INSET) - p.pos.x;
doCollision(p,3)
}else
if( p.pos.x > canvas.width-INSET){
box.x += (canvas.width - INSET) - p.pos.x;
doCollision(p,1)
}else
if(p.pos.y < INSET){
box.y += (INSET) -p.pos.y;
doCollision(p,0)
}else
if( p.pos.y > canvas.height-INSET){
box.y += (canvas.height - INSET) -p.pos.y;
doCollision(p,2)
}
drawVec(p.pos,p.velocity,"blue")
}
drawTempVecs();
ctx.globalAlpha = 1;
drawText(box.getDesc(),canvas.width/2,FONT_SIZE,FONT,FONT_SIZE,"black");
drawText("Click drag to apply force to box",canvas.width/2,FONT_SIZE +17,FONT,14,"black");
requestAnimationFrame(update)
}
update();
I am trying to create a simulation for experimenting with the Brownian motion. The idea is to make numerous particles (points) each moving randomly and colliding with each other, while conserving the total energy. That, I did succeed programming.
Now the problematic part is to make these point-like particles collide with a big circle and program the collision correctly. The difference is I made the particles move towards a totally random angle after their collision with each other, but when they collide with the circle, their previous path and speed, together with the circle's path and speed determines the result. So what I tried to do is to convert to a coordinate system attached to the center of the circle, calculate the collision point of every particle, which entered into the circle in the previous step, then calculate the angle in which they arrived. Now then the momentum of the particle is decomposed into two components: the tangential component remains the same, while the radial component changes according to the laws of collisions. This affects the motion of the circle as well. After calculating all the collisions, we move back to the original reference system, and draw all the objects.
The code can be found below, and it seems to be broken. I guess I was mistaken when calculating with the angles, because some particles can be shown arriving to the circle at one point, then "exiting" through the other side of it. I just lost track in following it all. I'm a complete newbie to programming, so be... um... gentle, please. :) Another strange thing is that the circle trends to move to a direction it started to move early, which is not what it supposed to do.
The mass of the particles are given, the mass of the circle is calculated with it's radius.
You can find the simulation on this website:
http://sixy.uw.hu/brown
It is not finished yet, but the start button works fine. :)
The part of the code I'm asking about is here: (shall I give you the whole code?)
function animateParticles() {
if ($('N').value != particleCount || $('S').value != circle.R) {
reset();
}
switch (activeGraph) {
case '#graph':
chartOptions.title.text = 'Távolság';
chartOptions.axisY.title = 'd [px]';
break;
if (Date.now() > lastAnim + 22) {
context.clearRect(0, 0, canvas.width, canvas.height);
// Particles step and collide
for (var i in particle) {
// Particle steps
{
particle[i].x += particle[i].vx;
particle[i].y += particle[i].vy;
}
// If not in the circle
if ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) > circle.R * circle.R) {
// Collides with some of the rest of the particles
for (var j = 1; j <= $('N').value; j += Math.ceil(particleCount / (Math.random() * 50 + 50))) {
// If that's not in the circle as well
if ((circle.x - particle[j].x) * (circle.x - particle[j].x) + (circle.y - particle[j].y) * (circle.y - particle[j].y) > circle.R * circle.R) {
// If not himself
if (j != i) {
if (particle[i].coll == 0) {
// Collide
if (Math.pow((particle[i].x - particle[j].x), 2) + Math.pow((particle[i].y - particle[j].y), 2) <= 4) {
var tkpx = (particle[i].vx + particle[j].vx) / 2;
var tkpy = (particle[i].vy + particle[j].vy) / 2;
var fi = Math.random() * 2 * Math.PI;
var index;
var ix = particle[i].vx;
var iy = particle[i].vy;
particle[i].vx = Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.cos(fi) + tkpx;
particle[i].vy = Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.sin(fi) + tkpy;
particle[j].vx = -Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.cos(fi) + tkpx;
particle[j].vy = -Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.sin(fi) + tkpy;
}
}
}
if (particle[i].coll >= 1) {
particle[i].coll -= 1;
}
}
}
}
// Collision with the walls
{
if (particle[i].x >= canvas.width) {
var temp;
temp = particle[i].x - canvas.width;
particle[i].x = canvas.width - temp;
particle[i].vx *= -1
}
if (particle[i].x <= 0) {
var temp;
temp = -particle[i].x;
particle[i].x = temp;
particle[i].vx *= -1;
}
if (particle[i].y >= canvas.height) {
var temp;
temp = particle[i].y - canvas.height;
particle[i].y = canvas.height - temp;
particle[i].vy *= -1;
}
if (particle[i].y <= 0) {
var temp;
temp = -particle[i].y;
particle[i].y = temp;
particle[i].vy *= -1;
}
}
}
//console.log(circle);
// Circle steps and collides with particles
var cu = 0;
var cv = 0;
for (var i in particle) {
if ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) < circle.R * circle.R) {
var pu;
var pv;
var px;
var py;
var px0;
var py0;
var t;
var t2;
var Tx;
var Ty;
var fi;
var p;
var q;
var cuu;
var cvv;
px = particle[i].x - circle.x;
py = particle[i].y - circle.y;
pu = particle[i].vx - circle.vx;
pv = particle[i].vy - circle.vy;
px0 = px - particle[i].vx;
py0 = py - particle[i].vy;
// Calculating the meeting point of the collision
t = (-(px0 * pu + py0 * pv) - Math.sqrt(circle.R * circle.R * (pu * pu + pv * pv) - Math.pow(px0 * pv - py0 * pu, 2))) / (pu * pu + pv * pv);
t2 = ((px - px0) * (px - px0) + (py - py0) * (py - py0)) / Math.sqrt(pu * pu + pv * pv) - t;
Tx = px0 + t * pu;
Ty = py0 + t * pv;
//console.log("TX: ", Tx);
//console.log("TY: ", Ty);
// Calculating the angle
{
if (Tx > 0 && Ty >= 0) {
fi = 2 * Math.PI - Math.atan(Ty / Tx);
}
else if (Tx < 0 && Ty >= 0) {
fi = Math.PI - Math.atan(Ty / Tx);
}
else if (Tx < 0 && Ty < 0) {
fi = Math.PI / 2 + Math.atan(Ty / Tx);
}
else if (Tx > 0 && Ty < 0) {
fi = -Math.atan(Ty / Tx);
}
else if (Tx = 0 && Ty >= 0) {
fi = 3 / 2 * Math.PI;
}
else if (Tx = 0 && Ty < 0) {
fi = Math.PI / 2;
}
}
//console.log("FI:", fi);
p = pu * Math.cos(fi) + pv * Math.sin(fi);
q = -pu * Math.sin(fi) + pv * Math.cos(fi);
cuu = 2 * q / (circle.M + 1);
cvv = 0;
q *= (1 - circle.M) / (1 + circle.M);
cu += cuu * Math.cos(-fi) + cvv * Math.sin(-fi);
cv += -cuu * Math.sin(-fi) + cvv * Math.cos(-fi);
//console.log("CU: ", cu);
//console.log("CV: ", cv);
pu = p * Math.cos(-fi) + q * Math.sin(-fi);
pv = -p * Math.sin(-fi) + q * Math.cos(-fi);
px = Tx + t2 * pu;
py = Ty + t2 * pv;
particle[i].x = px + circle.x;
particle[i].y = py + circle.y;
particle[i].vx = pu + circle.vx;
particle[i].vy = pv + circle.vy;
}
}
// Moving circle
{
circle.vx += cu;
circle.vy += cv;
circle.x += circle.vx;
circle.y += circle.vy;
for (var i in particle) {
while ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) < circle.R * circle.R) {
particle[i].x += circle.vx / Math.abs(circle.vx);
particle[i].y += circle.vy / Math.abs(circle.vy);
}
}
circlePath.push({x: circle.x, y: circle.y});
if (Date.now() > lastDraw + 500) {
items.push({
y: Math.sqrt((circle.x - canvas.width / 2) * (circle.x - canvas.width / 2) + (circle.y - canvas.height / 2) * (circle.y - canvas.height / 2)),
x: (items.length + 1) / 2
});
drawChart(activeGraph);
lastDraw = Date.now();
}
}
//console.log(circle);
drawCircle();
for (var i in particle) drawParticle(particle[i]);
lastAnim = Date.now();
}
animation = window.requestAnimationFrame(animateParticles);
}